函数式接口
1. 概念
函数式接口(functional interface)指只包含一个抽象方法的接口。不过,函数式接口依然可以有多个默认方法和类方法。
Lambda 表达式是 Java 8 的重要更新,其支持将代码块作为方法参数,允许使用更简洁的代码来创建函数式接口的实例。
Java8 专门为函数式接口提供了@FunctionalInterface
注解,该注解通常放在接口定义前面,其对程序功能没有任何作用,只是用于告诉编译器执行更严格的检查——检查该接口必须是函数式接口,否则编译器就会报错。
Java8 在 java.util.function
包下预定义了大量函数式接口,详见本章“一些Java常用接口”一节。
2. 函数描述符
函数式接口的抽象方法的签名基本上就是 Lambda 表达式的签名,我们将这种抽象方法叫作函数描述符。
这里约定一个特殊表示法来描述 Lambda 和函数式接口的签名,如:
() -> void
代表参数列表为空、且返回void
的函数(Apple, Apple) -> int
代表接受两个Apple
作为参数且返回int
的函数。
3. 原始类型特化
对于泛型函数式接口Predict<T>, Consumer<T>, Function<T, R>
等,由于泛型只能绑定到引用类型(这是由泛型内部的实现方式造成的),尽管Java有拆装箱机制,但装箱的本质就是把原始类型包裹起来,并保存在堆上,因此装箱后的值需要更多的内存,并需要额外的搜索来获取被包裹的原始值。因而,Java8为函数式接口提供了一个专门的版本,以便在输入和输出都是原始类型时避免自动装箱的操作,称为“原始类型特化”。
例如,在以下代码中,使用 IntPredict
就避免了对值1000进行装箱操作,但是用Predict\<Integer>
就会把参数1000
装箱到一个Integer
对象中:
public interface IntPredict{
boolean test(int t);
}
// 无装箱,返回true
IntPredict evenNumbers = (int i) -> i % 2 == 0;
evenNumbers.test(1000);
// 装箱,返回false
Predict<Integer> oddNumbers = (Integer i) -> i % 2 == 1;
oddNumbers.test(1000);
一般业说,针对专门的输入参数类型的函数式接口的名称都要加上对应的原始类型前缀,比如DoublePredict, IntConsumer, LongBinaryOperator, IntFunction
等。
4. 异常
请注意,任何函数式接口都不允许抛出检查型异常(checked exception),如果需要Lambda表达式来抛出异常,有两种方法:
- 定义一个自己的函数式接口,并声明受检异常;
- 把Lambda表达式包在一个
try/catch
语句块中。