06_fp_03_functional_interface

第六十二章 Functional Interface

Java 8 引入了Functional Interface的概念和@Functional标注(Annotation)。Functional Interface是一种特殊的接口(Interface),它只声明了一个抽象的方法,并且利用这个方法表达一种单函数调用的契约(Contract)。正是因为Functional Interface只有一个抽象的方法,编译器能够将Lambda表达式和Method Reference表达式转换成一个Functional Interface的实例。因此,Lambda表达式Method Reference表达式能像其他对象那样作为函数的参数传入,也可以作为返回值传出。

本章将详细的介绍Functional Interface的概念。

1 Functional Interface

Functional Interface是一种单抽象方法接口(Single Abstract Method interfaces, or SAM interfaces)。如果一个接口只定义了一个抽象函数,且该抽象函数不与Object类中的方法相同的话,这个接口被称为Functional Interface。这个抽象函数不能与Object类中的方法相同是因为Functional Interface需要声明一个新的方法。当(编译器/开发人员)生成一个Functional Interface的实例(Instance)时,这个实例是自动集成自Object类的。所以,如果这个抽象方法覆盖了Object类中的某一个方法,则这个Functional Interface并没有声明一个新的方法,从而编译器无法将Lambda表达式或者Method Reference表达式转换成这个Functional Interface的实例。如下面的例子所示,接口Runnable是一个Functional Interface;而接口NonFunc不是,因为NonFunc.equals()覆盖了Object.equals()方法。接口TwoFuncs也不是Functional Interface,因为它有两个抽象方法。

interface Runnable {
    // run()是唯一的一个抽象方法,且Object类中没有定义run()方法。
    void run();
}

interface NonFunc {
    boolean equals(Object obj); // 这个方法覆盖了Object.equals()方法
}

// TwoFuncs 接口声明了两个抽象方法,因此,它不是Functional Interface
interface TwoFuncs {
    void paint();
    void rotate();
}

但是,如果一个接口只声明了一个新的抽象方法,余下的方法覆盖了Object类的方法时,这个接口仍然被视为Functional Interface。

interface Runnable {
    void run();

    // 这个方法覆盖了Object.equals()方法,所以,它不是新方法
    boolean equals(Object obj); 
}

一个Functional Interface接口还可以实现多个default方法或者静态方法(static method)。因为default方法不是抽象的,因此,它不影响对Functional Interface的判定。(default方法是在接口(interface)中提供具体实现内容的方法。在接口中增加default方法不会改变代码的行为,因为如果子类没有提供具体的实现,则子类会使用default方法。如果子类已提供具体的实现,则会覆盖default方法。)

interface Runnable {
    void run();

    default void rotate() {
        System.out.println("It is rotating.");
    }

    static boolean isRunnable() {
        return true;
    }
}

使用@FunctionalInterface是可选的。这个标注并不影响对一个Functional Interface的判定。但是,使用@FunctionalInterface能有效的传递代码意图,而且编译器也会帮助开发人员检查这个接口是否是Functional Interface。

@FunctionalInterface
interface Runnable {
    void run();
}

2 Functional Interface使用场景

Java标准库提供了许多有用的Functional Interface接口供开发人员使用。我们将使用java.util.function.Function<T, R>和java.util.function.Predicate<T>来讲解如何使用Functional Interface。

java.util.function.Function<T, R>的一个实例表示着一个具体的函数实体。这个函数实体可以是一个Lambda表达式或者Method Reference表达式。Function接口的类型参数T和R分别表示着这个函数实体的输入参数类型和返回类型。例如,lenOfString1是一个Function接口的实例。它可以表示任何输入参数是String,返回类型是Integer的函数。因此,Lambda表达式s->s.length()符合这个要求,可以将其赋值给lenOfString1。在另一个例子中,String::length是一个Method Reference表达式,它指向String.length()成员方法。这个成员方法也是接受一个String对象作为入参,返回一个Integer类型。因此,它可以赋值给局部变量lenOfString2。

import java.util.function.Function;

public class FunctionalInterfaceExample {
    public static void main(String[] args) {
        //将Lambda表达式赋值给一个Function类型的局部变量 lenOfString1
        Function<String, Integer> lenOfString1 = s->s.length();

        //将Method Reference表达式赋值给一个Function类型的局部变量 lenOfString2
        Function<String, Integer> lenOfString2 = String::length;
    }
}

Lambda表达式和Method Reference表达式还可以通过Functional Interface机制,作为输入参数和返回值传递到函数中。例如,java.util.function.Predicate<T>用来表达一个判断式。它有唯一的一个抽象函数Predicate.test(T)。其他的函数(Predicate.and(), Predicate.isEquals(), Predicate.negate(), Predicate.or())要么是default method,要么是静态方法(static method)。在Collection类中,removeIf方法接受一个Predicate对象,用于判断是否将对象从该Collection中删除。List类继承了Collection.removeIf()方法。

在下面的代码中,isAdam和isDavid是两个判断式。他们可以想其他对象那样,调用方法,生成另一个判断式的实例。然后,aList.removeIf()方法的输入参数类型是一个判断式,因此,可以将isAdamOrDavid传入。Lambda表达式也能被自动的转换为Predicate<T>接口,所以,也能使用s->"Bob".equals(s)从aList中删除Bob。

import java.util.List;
import java.util.ArrayList;
import java.util.function.Predicate;

public class FunctionalInterfaceExample {
    public static void main(String[] args) {
        List<String> aList = new ArrayList<String>();
        aList.add("Adam");
        aList.add("Bob");
        aList.add("Cindy");
        aList.add("David");

        // 将 Lambda表达式转换为Predicate对象
        Predicate<String> isAdam = s->"Adam".equals(s);
        Predicate<String> isDavid = s->"Adam".equals(s);
        // 再由Predicate的方法,创建更复杂的Predicate对象
        Predicate<String> isAdamOrDavid = isAdam.or(isDavid);

        // 由Predicate对象删除"Adam"和"David"
        aList.removeIf(isAdamOrDavid);

        // 由Lambda表达式删除"Bob"
        aList.removeIf(s->"Bob".equals(s));
    }
}

3 总结

Functional Interface是面向对象程序设计和函数式程序设计的桥梁。它将Lambda表达式Method Reference表达式转换成了对象,从而,Lambda表达式和Method Reference表达式可以像对象那样赋值、传入函数、传出函数、和调用函数。因为,Lambda表达式和Method Reference表达式将会转换成对象,而这些对象都继承自Object类。所以,每个Functional Interface有且仅有一个新的抽象函数,并且这个抽象函数不能覆盖Object类中的任何方法。总之,Functional Interface的推出极大丰富了Java语言的特性和编码风格,也极大的增强了标准库的功能和使用方式,提高了开发人员的开发效率。

上一章
下一章

注册用户登陆后可留言

Copyright  2019 Little Waterdrop, LLC. All Rights Reserved.