函数式接口

在上个小节的最后,我们提到了函数式接口的概念,也知道了想要使用Lambda表达式,则必须依赖函数式接口。本小节我们将学习函数式接口相关的知识,包括什么是函数式接口为什么需要函数式接口如何自定义一个函数式接口如何创建函数式接口的对象,以及一些 Java 内置的函数式接口的详细介绍等。本小节内容较为简单,但需要读者有Lambda表达式前置知识,学习重点是要了解 Java 内置函数式接口。

1. 什么是函数式接口

函数是接口(Functional Interface)的定义非常容易理解:只有一个抽象方法的接口,就是函数式接口。可以通过Lambda表达式来创建函数式接口的对象。

我们来看一个在之前我们就经常使用的Runnable接口,Runnable接口就是一个函数式接口,下面的截图为 Java 源码:

我们看到Runnable接口中只包含一个抽象的run()方法,并且在接口上标注了一个@FuncationInterface注解,此注解就是 Java 8 新增的注解,用来标识一个函数式接口。

2. 为什么需要函数式接口

学习了这么久的 Java,我们对 Java 是纯种的面向对象的编程语言这一概念,可能有了一定的感触,在 Java 中,一切皆是对象。但是随着Pythonscala等语言的兴起,函数式编程的概念得到开发者们的推崇,Java 不得不做出调整以支持更广泛的技术要求。

在面向函数编程的语言中,Lambda表达式的类型就是函数,但是在 Java 中,Lambda表达式的类型是对象而不是函数,他们必须依赖于一种特别的对象类型——函数式接口。所以说,Java 中的Lambda表达式就是一个函数式接口的对象。我们之前使用匿名实现类表示的对象,都可以使用Lambda表达式来表示。

3. 自定义函数式接口

想要自定义一个函数式接口也非常简单,在接口上做两件事即可:

  1. 定义一个抽象方法:注意,接口中只能有一个抽象方法;
  2. 在接口上标记@FunctionalInterface注解:当然也可以不标记,但是如果错写了多个方法,编辑器就不能自动检测你定义的函数式接口是否有问题了,所以建议还是写上吧。
/**
 * 自定义函数式接口
 * @author colorful@TaleLin
 */
@FunctionalInterface
public interface FunctionalInterfaceDemo {

    void run();

}

由于标记了@FunctionalInterface注解,下面接口下包含两个抽象方法的这种错误写法,编译器就会给出提示:

4.创建函数式接口对象

在上面,我们自定义了一个函数式接口,那么如何创建它的对象实例呢?

我们可以使用匿名内部类来创建该接口的对象,实例代码如下:

/**
 * 测试创建函数式接口对象
 * @author colorful@TaleLin
 */
public class Test {

    public static void main(String[] args) {
        // 使用匿名内部类方式创建函数式接口
        FunctionalInterfaceDemo functionalInterfaceDemo = new FunctionalInterfaceDemo() {
            @Override
            public void run() {
                System.out.println("匿名内部类方式创建函数式接口");
            }
        };
        functionalInterfaceDemo.run();
    }

}

运行结果:

匿名内部类方式创建函数式接口

现在,我们学习了Lambda表达式,也可以使用Lambda表达式来创建,这种方法相较匿名内部类更加简洁,也更推荐这种做法。实例代码如下:

/**
 * 测试创建函数式接口对象
 * @author colorful@TaleLin
 */
public class Test {

    public static void main(String[] args) {
        // 使用 Lambda 表达式方式创建函数式接口
        FunctionalInterfaceDemo functionalInterfaceDemo = () -> System.out.println("Lambda 表达式方式创建函数式接口");
        functionalInterfaceDemo.run();
    }

}

运行结果:

Lambda 表达式方式创建函数式接口

当然,还有一种更笨的方法,写一个接口的实现类,通过实例化实现类来创建对象。由于比较简单,而且不符合我们学习函数式接口的初衷,这里就不再做实例演示了。

5. 内置的函数式接口介绍

通过上面一系列介绍和演示,相信对于函数式接口的概念和使用,你已经烂熟于心了。但是只知道这些还不够用,下面的内容才是本小节的重点,Java 中内置了丰富的函数式接口,位于java.util.function包下,学习这些函数式接口有助于我们理解 Java 函数式接口的真正用途和意义。

Java 内置了 4 个核心函数式接口:

  1. Comsumer<T>消费型接口: 表示接受单个输入参数但不返回结果的操作,包含方法:void accept(T t),可以理解为消费者,只消费(接收单个参数)、不返回(返回为 void);
  2. Supplier<T>供给型接口:表示结果的供给者,包含方法T get(),可以理解为供给者,只提供(返回T类型对象)、不消费(不接受参数);
  3. Function<T, R>函数型接口:表示接受一个T类型参数并返回R类型结果的对象,包含方法R apply(T t)
  4. Predicate<T>断言型接口:确定T类型的对象是否满足约束,并返回boolean值,包含方法boolean test(T t)

我们在 Java 的 api 文档中可以看到有一些方法的形参,会出现上面几类接口,我们在实例化这些接口的时候,就可以使用Lambda表达式的方式来实例化。

我们下面看几个实例,消费型接口使用实例:

实例演示
预览 复制