Java8引入了“行为参数化”的理念。为了实现行为参数化,java8提出函数式接口和Lambda表达式。本文首先会讲讲什么是函数式接口,然后会讲到java8预定义的四种核心函数式接口,以及使用这四种接口处理问题的demo。同时,本文还会使用大量的用Stream处理集合数据的例子。
函数式接口 functional interfaces
函数式接口:
- 函数式接口是一种特殊的SAM类型(Single Abstract Method), 即只定义一个抽象方法的接口。
- 使用
@FunctionalInterface
标注一个接口即表示该接口是一个函数式接口,如果你用@FunctionalInterface
定义了一个接口,而它却不是函数式接口的话,编译器将返回一个提示原因的错误。 - Lambda表达式是函数式接口的实例。以函数式接口为参数的方法,可以在调用时使用Lambda表达式作为参数。
之所以java8设计函数式接口,主要目的是因为:函数式接口的抽象方法可以使用Lambda表达式作为输入参数。本质上讲,函数式接口和Lambda表达式将行为参数化
成为了可能。
四种核心接口
Java8预定义了大量的函数式接口,这样客户端可以直接使用。这些预定义的函数式接口定义在java.util.function
下,通常分为以下四种。
函数式接口 | 参数及返回类型 | 用途 |
---|---|---|
Consumer |
void accept(T t) | 消费型接口,接受一个参数,没有返回值 |
Supplier |
T get() | 供给型接口,不接受参数,有一个返回值 |
Function |
R apply(T t) | 功能型接口,接受一个参数,处理后返回一个值 |
Predicate |
Boolean test(T t) | 断言性接口,接受一个参数,返回判断结果boolean |
Consumer
顾名思义,Consumer
接口使用场景为当一个对象需要被“消费”掉的时候。即这个对象作为方法的输入参数被执行某些操作,而且方法不做任何返回。打印操作就是一个典型的消费操作。
Consumer
源码:1
2
3
4@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
}
使用consumer
执行打印的demo1
2
3
4
5
6
7
8
9
10
11
12
13
14
15public class ConsumerExample{
public static void main(String args[]){
Consumer<Integer> consumer= i-> System.out.print(" "+i);
List<Integer> integerList=Arrays.asList(new Integer(1), new Integer(2), new Integer(3),
new Integer(4), new Integer(5), new Integer(6));
printList(integerList, consumer);
}
public static void printList(List<Integer> listOfIntegers, Consumer<Integer> consumer){
for(Integer integer:listOfIntegers){
consumer.accept(integer);
}
}
}
另外,Consumer
的doc提到一句,
Consumer is expected to operate via side-effects.
Consumer
接口可以执行带有副作用的操作,即Consumer
的操作可能会更改输入参数的内部状态。实践中,我们可以使用Consumer来更改对象内部状态。
例如Stream中使用率很高的forEach
方法。forEach
方法是java8新引入的内部遍历(有关内部遍历和外部遍历的区别,可以参见link).
举个例子:根据获取到的系统所有模块列表,创建一个map,map的key是模块id, value是模块对象。
1 | default Map<Integer, Module> getModules() { |
Supplier
当某个场景不需要输入但是需要输出的时候,就可以用到Supplier
。
Consumer
源码:1
2
3
4
5
6
7
8@FunctionalInterface
public interface Supplier<T> {
/**
* Gets a result.
* @return a result
*/
T get();
}
对于Supplier
,可以理解为利用它生产一个新的对象。例如通过实现Supplier
接口,可以自己来控制流的生成(generater方法)。
1 | //生成num个整数,并存入集合 |
Function
Function
接口主要用于映射场景. A类型的对象作为输入参数被执行Lambda表达式操作,最后转换为B类型的对象返回。
1 | @FunctionalInterface |
Function
接口被用在Stream的map方法的输入参数,map方法把input Stream的每一个元素,映射成output Stream的另外一个元素。
Stream的map
方法:1
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
举例,将列表的数字转换成平方数1
2List<Integer> nums = Arrays.asList(1,2,3,4);
List<Integer> squareNums = nums.stream().map(n ->n*n).collect(Collectors.toList);
再比如:
获取Person对象的姓名1
2
3
4
5
6
7
8
9
10
11
12List<Person> persons = Arrays.asList(
new Person("gsm", 26),
new Person("nx", 24),
);
String name = persons.stream()
.filter(x -> "gsm".equals(x.getName()))
.map(Person::getName) //convert stream to String
.findAny()
.orElse("");
System.out.println("name : " + name);
1 | 输出:name : gsm |
Predicate
Predicate
的使用场景为:一个对象需要被评估是否满足某条件,并且返回一个boolean型作为评估结果。
Predicate
源码:1
2
3
4
5
6package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
使用demo:自己定义一个以Predicate
为参数的的方法。1
2
3
4
5
6
7
8
9
10
11
12public int sumAll(List<Integer> numbers, Predicate<Integer> p) {
int total = 0;
for (int number : numbers) {
if (p.test(number)) {
total += number;
}
}
return total;
}
sumAll(numbers, n -> true); // 求数字列表的总和
sumAll(numbers, n -> n % 2 == 0); //求数字列表的偶数总和
sumAll(numbers, n -> n > 3);//求数字列表大于3的数字总和
再比如Stream中使用率很高的filter
方法。filter
方法返回一个由满足predicate条件的元素组成的列表。
1 | Stream<T> filter(Predicate<? super T> predicate); |
使用filter示例,找出名称为自定义模块2的模块。
1 | List<Module> moduleList = {new Module("自定义模块1"), new Module("自定义模块2",new Module("自定义模块3")}; |