Java8 Lambda 基础之函数接口

什么是函数接口?

函数接口英文全称是FunctionalInterface,是一种可用于Lambda表达式的接口,该概念在JDK 8首次被提出,关于FunctionalInterface JDK官方文档的解释是

* Conceptually, a functional interface has exactly one abstract

* method. Since {@linkplain java.lang.reflect.Method#isDefault()

* default methods} have an implementation, they are not abstract. If

* an interface declares an abstract method overriding one of the

* public methods of {@code java.lang.Object}, that also does

* <em>not</em> count toward the interface’s abstract method count

* since any implementation of the interface will have an

* implementation from {@code java.lang.Object} or elsewhere.

从中我们得知一个functional interface 有且只有一个抽像方法。比如我们常见的Runnable接口

@FunctionalInterface
public interface Runnable {
    /**  * When an object implementing interface <code>Runnable</code> is used  * to create a thread, starting the thread causes the object's  * <code>run</code> method to be called in that separately executing  * thread.  * <p>  * The general contract of the method <code>run</code> is that it may  * take any action whatsoever.  *  * @see java.lang.Thread#run()  */
    public abstract void run();
}

但functional interface中并不限制有非抽像方法存在比如Java8 后引进的default方法。以Stream.forEach中常用的Consumer接口为例,里面就带了一个andThen的非抽像方法。

@FunctionalInterface
public interface Consumer<T> {

    /**  * Performs this operation on the given argument.  *  * @param t the input argument  */
    void accept(T t);

  
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

除此之外functional interface还不限制存在Object类中定义的公有接口的方法,因为这本来就是Interface中隐式声明的。即使显示声明也不会计算进抽像方法中。比如JDK自带的Comparator里面就定义了两个抽像方法,但因为equals是Object类公有的方法。所以equals方法不会被计算进抽像方法个数。Comparator依然是个function interface。

@FunctionalInterface
public interface Comparator<T> {
    
     
    int compare(T o1, T o2);

    
    boolean equals(Object obj);
}

所以总的来说,函数接口是只有一个抽像方法,但可以抽像定义Object类公有方法和存在default方法的一类接口。

创建实例

一般接口实例的创建是implement该接口的对象,函数接口也不例外。但函数接口的特别之外在于Lambda表达式方面的应用,它可以被lambda expression、method references、 constructor references创建。可以说lambda表礞式创建的所有对象都是函数接口实例。

下面是三种创建的例子

  Runnable runnable = () -> System.out.println("lambda expression"); //lambda expression   Comparator<String> comparing = Comparator.comparing(String::length); //method references   BiConsumer<File, String> fileStringBiConsumer = File::new; //constructor references

另外函数接口之所以限制只有一个抽像方法的原因,是因为这个抽像方法代表了lambda expression要实现的函数类型。如果有多个抽像方法lambda expression根本不知道实现的是哪个方法。另外抽像方法的参数个类、参数类型以及返回值、异常定义是编译器进行类型推断的依据。

泛型问题

比如有以下三个接口:

@FunctionalInterface
public interface Foo extends Foo1, Foo2 {

    void bar(List args);
    
}

 interface Foo1 {
    void bar(List<String> args);
}
 interface Foo2 {
    void bar(List<Integer> args);

}

看上去Foo 继承了Foo1, Foo2两个接口。这两个接口又各自了一个抽像方法,Foo又定义了一个抽像方法。这样Foo就有三个抽像方法了,不可能是一个funtion interface。但实际上因为Java泛型会有一个类型擦除动作。所以这三个接口的方法实际都是:

    void bar(List args);

这样Foo的抽像方法相当于覆盖了Foo1,Foo2的接口中的抽像方法,就是只有一个。所以Foo可以看作是function inferface。

    原文作者:林林
    原文地址: https://zhuanlan.zhihu.com/p/58586114
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞