最近在做团队代码 code review 的时候发现了有人使用了 @FunctionalInterface 注解。我对这个注解不是很熟悉,基本上没用过。于是我就问对应的开发人员,给我讲一讲这个注解的用法,为什么要用这个注解?他讲的不太全,于是我就查询了一些资料,分享给大家!
在讲 @FunctionalInterface 之前,我们先熟悉一下,什么是函数式接口(Functional Interface)?
函数式接口(Functional Interface)
函数式接口(Functional Interface)是 Java 8对一类特殊类型的接口的称呼。 这类接口只定义了唯一的抽象方法的接口(除了隐含的Object对象的公共方法), 因此最开始也就做SAM类型的接口(Single Abstract Method)。
说白了,所谓的函数式接口,当然首先是一个接口,然后就是在这个接口里面只能有一个非 Object 对象的公共方法的抽象方法,可以有多个静态方法和默认方法。
上面说的是概念,如果你还没看懂,没关系,我们继续通过下面的几个例子,我相信你就会明白。
JDK 8之前已有的函数式接口
JDK 8之前已有的 JDK 中提供的支持函数式编程的函数式接口。
java.lang.Runnable
java.util.concurrent.Callable
java.security.PrivilegedAction
java.util.Comparator
java.io.FileFilter
java.nio.file.PathMatcher
java.lang.reflect.InvocationHandler
java.beans.PropertyChangeListener
java.awt.event.ActionListener
javax.swing.event.ChangeListener
Java8 中新定义的函数式接口
Java8 中有一个 java.util.function 包。其中定义了几组类型的函数式接口以及针对基本数据类型的子接口。
Predicate:传入一个参数,返回一个bool结果, 方法为boolean test(T t)
Consumer:传入一个参数,无返回值,纯消费。 方法为void accept(T t)
Function:传入一个参数,返回一个结果,方法为R apply(T t)
Supplier:无参数传入,返回一个结果,方法为T get()
UnaryOperator:一元操作符, 继承Function,传入参数的类型和返回类型相同。
BinaryOperator:二元操作符, 传入的两个参数的类型和返回类型相同, 继承 BiFunction
下面我们来看一个案例。
@FunctionalInterface
public interface XttblogService {
void sayMessage(String message);
}
那么我们现在就可以使用 Lambda 表达式来表示该接口的一个实现(注:JAVA 8 之前一般是用匿名类实现的):
XttblogService xttblogService = message -> System.out.println("Hello " + message);
@FunctionalInterface 注解的接口,只能有一个 public 接口。
@FunctionalInterface
如果定义了两个,就会报错。
但是我们可以定义多个默认方法。因为默认方法不是抽象方法,其有一个默认实现,所以是符合函数式接口的定义的。
@FunctionalInterface
public interface XttblogService{
void sayMessage(String message);
default void doSomeMoreWork1(){
// 业余草:www.xttblog.com
}
default void doSomeMoreWork2(){
}
}
除此之外,我们还可以定义多个静态方法。
@FunctionalInterface
public interface XttblogService {
void sayMessage(String message);
static void printHello(){
System.out.println("Hello");
}
static void xttblogHello(){
System.out.println("Xttblog Hello");
}
}
函数式接口里允许定义默认方法和静态方法,上面的两种写法都不会报错。
另外,函数式接口里还允许定义 java.lang.Object 里的 public 方法。
函数式接口里是可以包含 Object 里的 public 方法,这些方法对于函数式接口来说,不被当成是抽象方法(虽然它们是抽象方法);因为任何一个函数式接口的实现,默认都继承了 Object 类,包含了来自 java.lang.Object 里对这些抽象方法的实现。
@FunctionalInterface
public interface XttblogService {
void sayMessage(String message);
@Override
boolean equals(Object obj);
@Override
String toString();
@Override
int hashCode();
}
我们常用的一些接口 Callable、Runnable、Comparator 等在 JDK8 中都添加了 @FunctionalInterface 注解。
那么 Java 中为什么需要 @FunctionalInterface 注解呢?
没有这个注解,我们也可以实现 Lambda 表达式。
但是 Java 推出 @FunctionalInterface 注解的原因是在 Java Lambda 的实现中,开发组不想再为 Lambda 表达式单独定义一种特殊的 Structural 函数类型,称之为箭头类型(arrow type),依然想采用 Java 既有的类型系统(class, interface, method等)。增加一个结构化的函数类型会增加函数类型的复杂性,破坏既有的 Java 类型,并对成千上万的 Java 类库造成严重的影响。权衡利弊,因此最终还是利用 SAM 接口作为 Lambda 表达式的目标类型。
JDK 中已有的一些接口本身就是函数式接口,如 Runnable。JDK 8 中又增加了 java.util.function 包,提供了常用的函数式接口。
函数式接口代表的一种契约,一种对某个特定函数类型的契约。在它出现的地方,实际期望一个符合契约要求的函数。Lambda 表达式不能脱离上下文而存在,它必须要有一个明确的目标类型,而这个目标类型就是某个函数式接口。