1.4 函数式接口

问题

用户希望使用现有的函数式接口,或编写自定义函数式接口。

方案

创建只包含单一抽象方法的接口,并为其添加 @FunctionalInterface 注解。

讨论

Java 8 引入的函数式接口是一种包含单一抽象方法的接口,因此可以作为 lambda 表达式或方法引用的目标。

关键字 abstract 在这里很重要。在 Java 8 之前,接口中的所有方法被默认视为抽象方法,不需要为它们添加 abstract

我们定义一个名为 PalindromeChecker(回文检查器)的接口,如例 1-22 所示。

例 1-22 回文检查器接口

  1. @FunctionalInterface
  2. public interface PalindromeChecker {
  3. boolean isPalidrome(String s);
  4. }

由于接口中的所有方法均为 public 方法 4,可以省略访问修饰符,如同省略 abstract 关键字一样。

4至少在 Java 9 之前,接口中也允许使用 private 方法。更多详细信息,参见范例 10.2。

由于 PalindromeChecker 仅包含一个抽象方法,它属于函数式接口。Java 8 在 java.lang 包中提供了 @FunctionalInterface 注解,可以应用到函数式接口,如例 1-22 所示。

@FunctionalInterface 注解并非必需,但使用它是一种好习惯,原因有两点。首先,@FunctionalInterface 注解会触发编译时校验(compile-time check),有助于确保接口符合要求。如果接口不包含或包含多个抽象方法,程序将提示编译错误。

其次,添加 @FunctionalInterface 注解后,会在 Javadoc 中生成以下语句:

  1. Functional Interface:
  2. This is a functional interface and can therefore be used as the assignment
  3. target for a lambda expression or method reference.

函数式接口中同样可以使用 defaultstatic 方法。由于这两种方法都有相应的实现,它们与“仅包含一个抽象方法”的要求并不矛盾。示例代码如例 1-23 所示。

例 1-23 MyInterface 是一个包含静态方法和默认方法的函数式接口

  1. @FunctionalInterface
  2. public interface MyInterface {
  3. int myMethod();
  4. // int myOtherMethod(); ➋

  5. <b class="calibre4">default</b> String sayHello() {
  6.     <b class="calibre4">return</b> &#34;Hello, World!&#34;;
  7. }
  8. <b class="calibre4">static void</b> myStaticMethod() {
  9.     System.out.println(&#34;I&#39;m a static method in an interface&#34;);
  10. }
  11. }

❶ 单一抽象方法

❷ 如果这条语句未被注释掉,MyInterface 将不再是函数式接口

可以看到,如果 myOtherMethod 方法未被注释掉,MyInterface 就不再满足函数式接口的要求,@FunctionalInterface 注解将报错:“存在多个非重写的抽象方法。”

接口可以继承其他接口(甚至不止一个)。@FunctionalInterface 注解将对当前接口进行校验。因此,如果一个接口继承现有的函数式接口后,又添加了其他抽象方法,该接口就不再是函数式接口,如例 1-24 所示。

例 1-24 继承函数式接口的 MyChildInterface 不再属于函数式接口

  1. public interface MyChildInterface extends MyInterface {
  2. int anotherMethod();
  3. }

➊ 其他抽象方法

MyChildInterface 不属于函数式接口,因为它包含两个抽象方法:继承自 MyInterfacemyMethod 和声明的 anotherMethod。即便没有添加 @FunctionalInterface 注解,代码也可以编译,因为 MyChildInterface 就是一个标准接口,但无法作为 lambda 表达式的目标。

此外,还有一种不太常见的情况值得注意。Comparator 接口用于排序,其他范例将对此进行讨论。查看 Comparator 接口的 Javadoc 信息,并点击 Abstract Methods(抽象方法)标签后,将显示以下信息(图 1-1)。

{%}

图 1-1:Comparator 接口包含的抽象方法

可以看到,Comparator 接口包含两种抽象方法,且其中一种方法是在 java.lang.Object 类中实际实现的,那么 Comparator 为什么还属于函数式接口呢?

这里的特别之处在于,图中显示的 equals 方法来自 Object 类,因此已有一个默认的实现。根据 Javadoc 的描述,出于性能方面的考虑,用户可以提供满足相同契约(interface contract)的自定义 equals 方法,但不对该方法进行重写“始终是安全的”。

根据函数式接口的定义,Object 类中的方法与单一抽象方法的要求并不矛盾,因此 Comparator 仍然属于函数式接口。

另见

接口中默认方法的相关应用,参见范例 1.5。接口中静态方法的相关应用,参见范例 1.6。