在上一节中,我们为了使用Lambda表达式不得不创建了各种函数描述符的函数式接口,其实Java 8已经给我们提供了一套能够描述常见函数描述符的函数式接口。比如Predicate<T>
、Consumer<T>
、Function<T,R>
、Supplier<T>
等,这些函数式接口位于java.util.function
包。这一节主要记录这些函数式接口的应用。
Java8中的函数式接口
下表列出了Java8中常见的函数式接口:
函数式接口 | 函数描述符 | 原始类型特化 |
---|---|---|
Predicate | T->boolean | IntPredicate,LongPredicate, DoublePredicate |
Consumer | T->void | IntConsumer,LongConsumer, DoubleConsumer |
Function<T,R> | T->R | IntFunction LongFunction DoubleFunction |
Supplier | ()->T | BooleanSupplier,IntSupplier, LongSupplier, DoubleSupplier |
UnaryOperator | T->T | IntUnaryOperator, LongUnaryOperator, DoubleUnaryOperator |
BinaryOperator | (T,T)->T | IntBinaryOperator, LongBinaryOperator, DoubleBinaryOperator |
BiPredicate<L,R> | (L,R)->boolean | |
BiConsumer<T,U> | (T,U)->void | ObjIntConsumer |
BiFunction<T,U,R> | (T,U)->R | ToIntBiFunction<T,U>, ToLongBiFunction<T,U>, ToDoubleBiFunction<T,U> |
Predicate
predicate: 英 [ˈpredɪkət] 美 [ˈpredɪkət] 断言,断定的意思。从接口的名称就可以推断出这个函数式接口的主要作用就是用于判断作用,Predicate源码如下所示:
1 |
|
可看到java.util.function.Predicate<T>
接口定义了一个名叫test
的抽象方法,它接受泛型T
对象,并返回一个boolean
,函数描述符为(T) -> boolean
举几个例子:
1 | // 偶数判断 |
除了抽象方法外,java.util.function.Predicate<T>
接口还定义了三个默认方法:and
,negate
和or
,对应“与”,“非”和“或”操作,这样我们便可以复合Lambda表达式了,比如:
1 | // 判断是偶数,并且大于30 |
Consumer
英 [kənˈsju:mə(r)] 美 [kənˈsu:mə(r)] n.消费者。该函数式接口用于消费一个对象,即接收一个对象,对其执行某些操作,然后没有返回值。Consumer源码如下所示:
1 |
|
可看到java.util.function.Consumer<T>
定义了一个名叫accept
的抽象方法,它接受泛型T
的对象,没有返回(void
),函数描述符为(T) -> void
。其还提供了一个默认方法andThen
。举个例子:
1 | Consumer<Apple> printAppleColor = (a)-> System.out.println(a.getColor()); |
Supplier
supplier 英 [səˈplaɪə(r)] 美 [səˈplaɪər] n.供应商;供应者;供给者。其源码如下:
1 |
|
可看到java.util.function.Supplier<T>
定义了一个名叫get
的抽象方法,它不接收参数,返回泛型T
的对象,函数描述符为() -> T
。举个例子:
1 | Supplier<Person> personSupplier = Person::new; |
Functions
Functions源码如下:
1 |
|
java.util.function.Function<T, R>
接口定义了一个叫作apply
的方法,它接受一个泛型T
的对象,并返回一个泛型R
的对象,函数描述符为(T) -> R
。举个例子:
1 | Function<Apple, Double> getAppleWeight = (a) -> { |
Functions接口还提供了两个抽象方法compose
和andThen
,从源码可以看出两者的根本区别。举个compose
例子:
1 | Function<Integer, Integer> f = (x) -> x + 1; |
过程为:f(g(2))
,也就是1+(2*2)
。
举个andThen的例子:
1 | Function<Integer, Integer> f = (x) -> x + 1; |
过程为:g(f(2))
,也就是(2+1)*2
。
原始类型特化
在学习Function接口的时候,我们定义了f
函数:
1 | Function<Integer, Integer> f = (x) -> x + 1; |
x的类型为Integer类型,1为int类型,返回值为Integer类型,整个过程实际上为Integer.valueOf(x.intValue() + 1)
。虽然编译器可以自动帮我们完成拆装箱,但这会造成不必要的性能消耗。考虑到了这一点,Java8为我们提供了int类型的Function接口:IntFunction:
1 |
|
所以f
最好重构为:
1 | IntFunction<Integer> f = (x) -> x + 1; |
剩余的原始类型特化函数式接口可参考上面的表格。
Java8中增强的Comparator
在Java8之前,Comparator接口用于实现简单的比较排序算法。比如有如下List:
1 | List<Double> list = new ArrayList<>(); |
使用Comparator接口对其从小到大排序:
1 | Collections.sort(list, new Comparator<Double>() { |
Comparator接口也是一个函数式接口,函数描述符为(T,T) -> int
,Java8中可以使用Lambda改造上面的排序方法:
1 | Collections.sort(list, (o1, o2) -> o1.compareTo(o2)); |
Java8对List提供了sort
方法,可以替代Collections.sort
,所以上面的代码可以简化为:
1 | list.sort((o1, o2) -> o1.compareTo(o2)); |
使用方法的引用来进一步简化:
1 | list.sort(Double::compareTo); |
Java8对Comparator进行了增强,加入了一些实用的默认方法,比如对排序结果反转:
1 | Comparator<Double> comparator = Double::compareTo; |
更多方法可以参考Comparator接口的JavaDoc。
查看Comparator的时候发现其虽然是函数式接口,但是却包含了compare
和equals
这两个抽象方法,顿时有点懵逼,函数式接口不是只能有一个抽象方法么?查找资料后发现:函数式接口中可以额外定义多个抽象方法,但这些抽象方法签名必须和Object的public方法一样,接口最终有确定的类实现,而类的最终父类是Object。因此函数式接口可以定义Object的public方法。
《Java 8实战》读书笔记