@FunctionalInterface、Lambda表达式和方法引用

知识不回顾是会被遗忘的!

网上看了一些相关文章,这里记录一下,仅供参考

Java语言从JDK1.8开始引入了函数式编程。

        函数式编程的核心特点是,函数作为一段功能代码,可以像变量一样进行引用和传递,以便在有需要的时候进行调用。

1. @FunctionalInterface与“函数类型”

        Java对函数式编程的支持,本质是通过接口机制来实现的。首先定义一个仅声明一个方法的接口,然后对接口冠以@FunctionalInterface注解,那么这个接口就可以作为“函数类型”,可以接收一段以Lambda表达式,或者方法引用予以承载的逻辑代码。例如:

@FunctionalInterface
interface IntAdder {
    int add(int x, int y);
}

IntAdder adder = (x, y) -> x + y;

IntAdder 就可以看成是一个“函数类型”。Lambda表达式和方法引用的介绍见后文。

概念如此,需要思考的有几点:

为什么必须是只声明一个方法的接口?
        显然这个方法就是用来代表“函数类型”所能执行的功能,一个函数一旦定义好,它能执行的功能是确定的,就是调用和不调用的区别。接口中声明的方法就是和函数体定义一一对应的。
        事实上,@FunctionalInterface下只能声明一个方法,多一个、少一个都不能编译通过 。覆写Object中toString/equals的方法不受此个数限制。

比如Comparator接口就声明了2个方法:

// Comparator.java
@FunctionalInterface
public interface Comparator {
    int compare(T o1, T o2);
    boolean equals(Object obj);
    //...
}

        严格地说,@FunctionalInterface下只能声明一个未实现的方法,default方法和static方法因为带有实现体,所有不受此限制。

  @FunctionalInterface
   public interface IAdd {
       R add(T t1, T t2);
       
       default R test1(T t1, T t2) {//可以额外定义default方法
           return null;
       }
       
       static  R test2(T t1, T t2) {//可以额外定义static方法
           return null;
       }
   }

        关于interface中声明default/static方法有疑虑的话,可以查阅博主另一篇文章:java接口里面可以有成员变量么?

        @FunctionalInterface注解不是必须的,不加这个注解的接口(前提是只包含一个方法)一样可以作为函数类型。不过,显而易见的是,加了这个注解表意更明确、更直观,是更被推荐的做法。
        要定义清楚一个函数类型,除了函数名称,必须明确规定函数的参数个数和类型、返回值类型,这些信息都是包含于接口中声明的方法。


2. JDK提供的“函数类型”

java.util.function包下预定义了常用的函数类型,包括:

@FunctionalInterface
public interface Consumer {
    void accept(T t); //接收一个类型为T(泛型)的参数,无返回值;所以叫消费者
}
@FunctionalInterface
public interface BiConsumer {
    void accept(T t, U u);//接收2个参数,无返回值
}
@FunctionalInterface
public interface Supplier {
    T get();//无参数,有返回值(所以叫提供者)
}
//注意没有BiSupplier,因为返回值只能有1个,不会有2个
@FunctionalInterface
public interface Function {
    R apply(T t);//一个输入(参数),一个输出(返回值)
}
@FunctionalInterface
public interface BiFunction {
    R apply(T t, U u);//两个输入T和U,一个输出R
}
@FunctionalInterface
public interface UnaryOperator extends Function {
    static  UnaryOperator identity() {//一元操作,输入原样返回给输出
        return t -> t;
    }
}
@FunctionalInterface
public interface BinaryOperator extends BiFunction {//二元操作,输入输出类型相同
    public static  BinaryOperator minBy(Comparator comparator) {
        Objects.requireNonNull(comparator);
        return (a, b) -> comparator.compare(a, b) <= 0 ? a : b;//传入比较器,返回较小者
    }
    public static  BinaryOperator maxBy(Comparator comparator) {
        Objects.requireNonNull(comparator);
        return (a, b) -> comparator.compare(a, b) >= 0 ? a : b;//传入比较器,返回较大者
    }
}

        这些个定义,都是在参数个数(0,1,2)和有无返回值上做文章。另外还有一些将泛型类型具体化的衍生接口,比如Predicate、LongSupplier等等。

@FunctionalInterface
public interface Predicate {
    boolean test(T t);//输入1个参数,返回boolean,就好比是预言家,预言你这个输入是真还是假
}
@FunctionalInterface
public interface LongSupplier {
    long getAsLong();//没有输入,输出long类型(long类型的提供者)
}

3. Lambda表达式

        上面弄清楚了函数类型@FunctionalInterface,那么函数类型能接收怎么样的函数实现体呢?怎么接收呢?该Lambda出场了。

        Lambda表达式能赋值给一个变量,也就能当作参数传给函数。这个Lambda形式的变量/参数的类型是它所实现的那个接口,所包含的方法体便是这个接口抽象方法的实现。以后看到调用方法的参数是一个SAM类型接口的时候就可以考虑使用Lambda表达式替换匿名内部类来写。

作用:

  • ①减少代码量,突出代码意图
  • ②对集合数据 Collection 操作更简便
  • ③使变量记住一段逻辑:

        任务逻辑传递(传递一段运算逻辑给执行者)

        回调逻辑传递(简化接口回调的时候 new匿名类后实现抽象方法的模版代码)

将一个方法写成Lambda表达式,只需要关注参数列表和方法体。        

@FunctionalInterface、Lambda表达式和方法引用_第1张图片

语法组成:

(参数类型 参数名) -> {
        方法体;

        return 返回值;

}

  • ①形式参数:最前面的部分是一对括号,里面是参数,无参数就是一对空括号
  • ②箭头:中间的是 -> ,用来分割参数和body部分,读作“ goes to”
  • ③方法体:body部分可以是一个表达式或者一个代码块。

简写:

  • ①可选类型声明:不用声明参数类型,编译器可以自动识别。
  • ②可选参数括号:一个参数无需定义括号,多个参数需要定义。
  • ③可选大括号:如果body部分只包含一个语句或表达式,就不需要使用大括号括起来。
  • ④可选 return 关键字:如果body部分是一个表达式,表达式的值会被作为返回值返回;如果是代码块,需要用 return 指定返回值

        Java8 之前创建接口实现类总会有很多冗余的模版代码,接口中定义的抽象方法越多,每次实现的模版代码就越多,而很多时候这个接口实现类只需要用到一次。 

@FunctionalInterface、Lambda表达式和方法引用_第2张图片

变量作用域 :

  • ①局部变量:引用的局部变量不可被修改值,即必须是final所修饰的,或者不被后面代码更改的(隐形final属性)。
  • ②成员变量:可以修改类成员变量(非 final 修饰的)和静态变量。
  • ③在Lambda表达式中,不允许声明一个与局部变量同名的参数或者局部变量。
  • ④在Lambda表达式中,使用this调用的是该Lambda表达式所属方法的所属实例的this。
  • ⑤在Lambda表达式中,无法访问到自身接口的默认方法(不存在实例)。

        Lambda表达式用来定义函数实现体。有很多种写法(都是为了简化书写),但核心是通过->连接参数和实现代码:

(入参)->{实现代码}

//无返回值的时候
(int x)->{System.out.println(x);}
(x)->{System.out.println(x);}//参数类型自动推断
x->{System.out.println(x);}//只有一个参数的时候,可以省略小括号
x->System.out.println(x);//实现体只有一个表达式可以省略大括号,System.out.println本身无返回值

//有返回值的情况
(int x)->{return x*x;}
(x)->{return x*x;}
//x->return x*x; //错误,不能这么写!!
x->x*x;

说了这么多,来实操一把:

IntConsumer ic = x->System.out.println(x);
IntFunction ifi1 = x->{return x*x;};
IntFunction ifi2 = x->x*x;
ic.accept(100);//100
System.out.println(ifi1.apply(5));//25
System.out.println(ifi2.apply(5));//25

好了,函数类型–>Lambda表达式说明白了,再来看看方法引用是怎么回事。

4. 四种方法引用

        文章开头说过了,函数类型可以接收一段Lambda表达式,或者对方法的引用。方法引用就是对一个类中已经存在的方法加以引用,分4中类型:(以Test类为例)

  1. 对类构造方法的引用,如Test::new。
  2. 对类静态方法的引用,如Test::staticMethodName
  3. 对对象实例方法的引用,如:new Test()::instanceMethod
  4. 是2和3的结合,如Test::instanceMethod2,但要求函数类型声明和函数调用的时候,其第一个参数必须是Test类的实例。

第4种比较难以说清楚,看看下面的例子吧:

public class Test {
    private String name = "";
    
    public Test() {
        System.out.println("构造方法:无参数");
    }

    public Test(String name) {
        this.name = name;
        System.out.println("构造方法:参数="+name);
    }
    
    public static void staticMethod(String str) {
        System.out.println("static method: input=" + str);
    }
    
    public void instanceMethod(String str) {
        System.out.println("instance method: input=" + str);
    }
    
    public static void main(String[] args) {
        Supplier s1 = Test::new;//对无参构造器的引用,无参构造器其实就是一个对象的Supplier(提供者)
        s1.get();//调用构造方法:无参数
        
        Function f1 = Test::new;//引用有一个String参数的构造器
        f1.apply("Test");//调用构造方法:参数=Test
        
        Consumer c1 = Test::staticMethod;//对静态方法引用
        c1.accept("1");//static method: input=1
        
        Consumer c2 = new Test()::instanceMethod;//对实例方法的引用
        c2.accept("2");//instance method: input=2
        
        //第4种
        BiConsumer bc1 = Test::instanceMethod;
        bc1.accept(new Test(), "3");//instance method: input=3
    }
} 

第4中方法引用,本质上是对实例方法的引用,只不过是在调用的时候才传入那个实例对象。

5. andThen链式表达

JDK中很多函数类型,都实现了default的andThen方法,可以将多个函数体(Lambda表达式、方法引用)串起来,方便进行链式调用。
调用链上的任何一个抛出异常,整个调用链会提前结束,异常由调用者处理。

/**
 * 通过andThen()进行链式操作
 */
@Test
public void testLinkConsumer() {
    IntConsumer action = x-> System.out.print(x);
    action = action.andThen(x->System.out.print("--tail1"))
            .andThen(x->System.out.print("--tail2"));
    //100--tail1--tail2
    action.accept(100);
}

6. 最后

小插曲:Callable和Runnable到底什么区别?

//java.util.concurrent.Callable
@FunctionalInterface
public interface Callable {
    V call() throws Exception;
}
//java.lang.Runnable
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

这两者也都是JDK预定义的函数接口,两者都不接收参数,主要用于多线程编程。

Runnable无返回值,一般用于new一个新线程的时候,在新线程中执行代码。

Callable一样一般用于在新线程中执行,只不过执行成功后有返回值,如果执行失败还会抛异常。

最后,一起分析:

Callable c1 = ()->1;
Callable c2 = ()->c1.call();

c1引用了一个Lambda表达式;

c2引用了一个新的Lambda表达式,表示式的实现代码中调用了c1提供的call()方法,并将call()方法的返回值返回。

Callable c1 = ()->1;
Callable c2 = ()->c1.call();
Callable c3 = ()->{
    System.out.println("c3 call c1");
    return c1.call();
};

try {
    System.out.println(c1.call());//1
    System.out.println(c2.call());//1
    System.out.println(c3.call());//c3 call c1
    //1
} catch (Exception e) {
    e.printStackTrace();
}

注意,c3和c2本质上是一样的,只不过方法实现上,多加了一行打印代码。

7、Lambda 表达式的各种形态和使用场景

        Lambda 表达式是 Java 8 中添加的功能。引入 Lambda 表达式的主要目的是为了让 Java 支持函数式编程。 Lambda 表达式是一个可以在不属于任何类的情况下创建的函数,并且可以像对象一样被传递和执行。

Java lambda 表达式用于实现简单的单方法接口,与 Java Streams API 配合进行函数式编程

        在前几篇关于 List、Set 和 Map 的文章中,我们已经看到了这几个 Java 容器很多操作都是通过 Stream 完成的,比如过滤出对象 List 中符合条件的子集时,会使用类似下面的 Stream 操作。

List list = aList.filter(a -> a.getId() > 10).collect(Colletors.toList);

        其中filter方法里用到的a -> a.getId() > 10就是一个 Lambda 表达式,前面对用到 Lambda 的地方知识简单的说了一下,如果你对各种 Stream 操作有疑问,可以先把本篇 Lambda 相关的内容学完,接下来再仔细梳理 Stream 时就会好理解很多了。

@FunctionalInterface、Lambda表达式和方法引用_第3张图片

Lambda 表达式和函数式接口

        上面说了 lambda 表达式便于实现只拥有单一方法的接口,同样在 Java 里匿名类也用于快速实现接口,只不过 lambda 相较于匿名类更方便些,在书写的时候连创建类的步骤也免去了,更适合用在函数式编程。

        举个例子来说,函数式编程经常用在实现事件 Listener 的时候 。 在 Java 中的事件侦听器通常被定义为具有单个方法的 Java 接口。下面是一个 Listener 接口示例:

public interface StateChangeListener {
    public void onStateChange(State oldState, State newState);
}

        上面这个 Java 接口定义了一个只要被监听对象的状态发生变化,就会调用的 onStateChange 方法(这里不用管监听的是什么,举例而已)。 在 Java 8 版本以前,监听事件变更的程序必须实现此接口才能侦听状态更改。

比如说,有一个名为 StateOwner 的类,它可以注册状态的事件侦听器。

public class StateOwner {
    public void addStateListener(StateChangeListener listener) { ... }
}

我们可以使用匿名类实现 StateChangeListener 接口,然后为 StateOwner 实例添加侦听器。

StateOwner stateOwner = new StateOwner();

stateOwner.addStateListener(new StateChangeListener() {

    public void onStateChange(State oldState, State newState) {
        // do something with the old and new state.
        System.out.println("State changed")
    }
});

        在 Java 8 引入Lambda 表达式后,我们可以用 Lambda 表达式实现 StateChangeListener 接口会更加方便。

        现在,把上面例子接口的匿名类实现改为 Lambda 实现,程序会变成这样:

StateOwner stateOwner = new StateOwner();

stateOwner.addStateListener(
    (oldState, newState) -> System.out.println("State changed")
);

在这里,我们使用的 Lambda 表达式是:

(oldState, newState) -> System.out.println("State changed")

        这个 lambda 表达式与 StateChangeListener 接口的 onStateChange() 方法的参数列表和返回值类型相匹配如果一个 lambda 表达式匹配单方法接口中方法的参数列表和返回值(比如本例中的 StateChangeListener 接口的 onStateChange 方法),则 lambda 表达式将转换为拥有相同方法签名的接口实现 这句话听着有点绕,下面详细解释一下 Lambda 表达式和接口匹配的详细规则。

匹配Lambda 与接口的规则

        上面例子里使用的 StateChangeListener 接口有一个特点,其只有一个未实现的抽象方法,在 Java 里这样的接口也叫做函数式接口 (Functional Interface)。将 Java lambda 表达式与接口匹配需要满足一下三个规则:

  • 接口是否只有一个抽象(未实现)方法,即是一个函数式接口
  • lambda 表达式的参数是否与抽象方法的参数匹配
  • lambda 表达式的返回类型是否与单个方法的返回类型匹配

如果能满足这三个条件,那么给定的 lambda 表达式就能与接口成功匹配类型。

函数式接口

        只有一个抽象方法的接口被称为函数是式接口,从 Java 8 开始,Java 接口中可以包含默认方法和静态方法。默认方法和静态方法都有直接在接口声明中定义的实现。这意味着,Java lambda 表达式可以实现拥有多个方法的接口——只要接口中只有一个未实现的抽象方法就行

        所以在文章一开头我说lambda 用于实现单方法接口,是为了让大家更好的理解,真实的情况是只要接口中只存在一个抽象方法,那么这个接口就能用 lambda 实现。

        换句话说,即使接口包含默认方法和静态方法,只要接口只包含一个未实现的抽象方法,它就是函数式接口。比如下面这个接口:

import java.io.IOException;
import java.io.OutputStream;

public interface MyInterface {

    void printIt(String text);

    default public void printUtf8To(String text, OutputStream outputStream){
        try {
            outputStream.write(text.getBytes("UTF-8"));
        } catch (IOException e) {
            throw new RuntimeException("Error writing String as UTF-8 to OutputStream", e);
        }
    }

    static void printItToSystemOut(String text){
        System.out.println(text);
    }
}

即使这个接口包含 3 个方法,它也可以通过 lambda 表达式实现,因为接口中只有一个抽象方法 printIt没有被实现

MyInterface myInterface = (String text) -> {
    System.out.print(text);
};

Lambda VS 匿名类

        尽管 lambda 表达式和匿名类看起来差不多,但还是有一些值得注意的差异。 主要区别在于,匿名类可以有自己的内部状态--即成员变量,而 lambda 表达式则不能。

public interface MyEventConsumer {
    public void consume(Object event);
}

比如上面这个接口,通过匿名类实现

MyEventConsumer consumer = new MyEventConsumer() {
    public void consume(Object event){
        System.out.println(event.toString() + " consumed");
    }
};

MyEventConsumer 接口的匿名类可以有自己的内部状态。

MyEventConsumer myEventConsumer = new MyEventConsumer() {
    private int eventCount = 0;
    public void consume(Object event) {
        System.out.println(event.toString() + " consumed " + this.eventCount++ + " times.");
    }
};

        我们给匿名类,加了一个名为 eventCount 的整型成员变量,用来记录匿名类 consume 方法被执行的次数。Lambda 表达式则不能像匿名类一样添加成员变量,所以也成 Lambda 表达式是无状态的。

推断 Lamdba 的接口类型

        使用匿名类实现函数式接口的时候,必须在 new 关键字后指明实现的是哪个接口。比如上面使用过的匿名类例子

stateOwner.addStateListener(new StateChangeListener() {

    public void onStateChange(State oldState, State newState) {
        // do something with the old and new state.
    }
});

        但是 lambda 表达式,通常可以从上下文中推断出类型。例如,可以从 addStateListener() 方法声明中参数的类型 StateChangeListener 推断出来,Lambda 表达式要实现的是 StateChangeListener 接口。

stateOwner.addStateListener(
    (oldState, newState) -> System.out.println("State changed")
);

        通常 lambda 表达式参数的类型也可以推断出来。在上面的示例中,编译器可以从StateChangeListener 接口的抽象方法 onStateChange() 的方法声明中推断出参数 oldState 和 newState 的类型。

Lambda 的参数形式

        由于 lambda 表达式实际上只是个方法,因此 lambda 表达式可以像方法一样接受参数。Lambda 表达式参数根据参数数量以及是否需要添加类型会有下面几个形式。

如果表达式的方法不带参数,那么可以像下面这样编写 Lambda 表达式:

() -> System.out.println("Zero parameter lambda");

如果表达式的方法接受一个参数,则可以像下面这样编写 Lambda 表达式:

(param) -> System.out.println("One parameter: " + param);

当 Lambda 表达式只接收单个参数时,参数列表外的小括号也可以省略掉。

param -> System.out.println("One parameter: " + param);

        当 Lambda 表达式接收多个参数时,参数列表的括号就没法省略了。

        如果编译器无法从 Lambda 匹配的函数式接口的方法声明推断出参数类型(出现这种情况时,编译器会提示),则有时可能需要为 Lambda 表达式的参数指定类型。

(Car car) -> System.out.println("The car is: " + car.getName());

Lambda 的方法体

lambda 表达式的方法的方法体,在 Lambda 声明中的 -> 右侧指定:

(oldState, newState) -> System.out.println("State changed")

如果 Lambda 表达式的方法体需要由多行组成,则需要把多行代码写在用{ }括起来的代码块内。

(oldState, newState) -> {
    System.out.println("Old state: " + oldState);
    System.out.println("New state: " + newState);
}

Lamdba 表达式的返回值

        可以从 Lambda 表达式返回值,就像从方法中返回值一样。只需在 Lambda 的方法体中添加一个 return 语句即可:

(param) -> {
    System.out.println("param: " + param);
    return "return value";
}

        如果 Lambda 表达式所做的只是计算返回值并返回它,我们甚至可以省略 return 语句。

(a1, a2) -> { return a1 > a2; }
// 上面的可以简写成,不需要return 语句的
(a1, a2) -> { a1 > a2; }

        Lambda 表达式本质上是一个对象,跟其他任何我们使用过的对象一样, 我们可以将 Lambda 表达式赋值给变量并进行传递和使用。

public interface MyComparator {

    public boolean compare(int a1, int a2);

}

---

MyComparator myComparator = (a1, a2) -> a1 > a2;

boolean result = myComparator.compare(2, 5);

        上面的这个例子展示 Lambda 表达式的定义,以及如何将 Lambda 表达式赋值给给变量,最后通过调用它实现的接口方法来调用 Lambda 表达式。

外部变量在 Lambda 内的可见性

        在某些情况下,Lambda 表达式能够访问在 Lambda 函数体之外声明的变量。 Lambda 可以访问以下类型的变量:

  • 局部变量
  • 实例变量
  • 静态变量

Lambda 内访问局部变量,Lambda 可以访问在 Lambda 方法体之外声明的局部变量的值

public interface MyFactory {
    public String create(char[] chars);
}

String myString = "Test";

MyFactory myFactory = (chars) -> {
    return myString + ":" + new String(chars);
};

Lambda 访问实例变量,Lambda 表达式还可以访问创建了 Lambda 的对象中的实例变量。

public class EventConsumerImpl {

    private String name = "MyConsumer";

    public void attach(MyEventProducer eventProducer){
        eventProducer.listen(e -> {
            System.out.println(this.name);
        });
    }
}

        这里实际上也是 Lambda 与匿名类的差别之一。匿名类因为可以有自己的实例变量,这些变量通过 this 引用来引用。但是,Lambda 不能有自己的实例变量,因此 this 始终指向外面包裹 Lambda 的对象。

Lambda 访问静态变量,Lambda 表达式也可以访问静态变量。这也不奇怪,因为静态变量可以从 Java 应用程序中的任何地方访问,只要静态变量是公共的。

public class EventConsumerImpl {
    private static String someStaticVar = "Some text";

    public void attach(MyEventProducer eventProducer){
        eventProducer.listen(e -> {
            System.out.println(someStaticVar);
        });
    }
}

把方法引用作为 Lambda

        如过编写的 lambda 表达式所做的只是使用传递给 Lambda 的参数调用另一个方法,那么 Java里为 Lambda 实现提供了一种更简短的形式来表达方法调用。比如说,下面是一个函数式数接口:

public interface MyPrinter{
    public void print(String s);
}

接下来我们用 Lambda 表达式实现这个 MyPrinter 接口

MyPrinter myPrinter = (s) -> { System.out.println(s); };

因为 Lambda 的参数只有一个,方法体也只包含一行,所以可以简写成

MyPrinter myPrinter = s ->  System.out.println(s);

        又因为 Lambda 方法体内所做的只是将字符串参数转发给 System.out.println() 方法,因此我们可以将上面的 Lambda 声明替换为方法引用。

MyPrinter myPrinter = System.out::println;

注意双冒号 :: 向 Java 的编译器指明这是一个方法的引用。引用的方法是双冒号之后的方法。而拥有引用方法的类或对象则位于双冒号之前。

我们可以引用以下类型的方法:

  • 静态方法
  • 参数对象的实例方法
  • 实例方法
  • 类的构造方法

引用类的静态方法

最容易引用的方法是静态方法,比如有这么一个函数式接口和类

public interface Finder {
    public int find(String s1, String s2);
}

public class MyClass{
    public static int doFind(String s1, String s2){
        return s1.lastIndexOf(s2);
    }
}

如果我们创建 Lambda 去调用 MyClass 的静态方法 doFind

Finder finder = (s1, s2) -> MyClass.doFind(s1, s2);

所以我们可以使用 Lambda 直接引用 Myclass 的 doFind 方法。

Finder finder = MyClass::doFind;

引用参数的方法

接下来,如果我们在 Lambda 直接转发调用的方法是来自参数的方法

public interface Finder {
    public int find(String s1, String s2);
}

Finder finder = (s1, s2) -> s1.indexOf(s2);

依然可以通过 Lambda 直接引用

Finder finder = String::indexOf;

        这个与上面完全形态的 Lambda 在功能上完全一样,不过要注意简版 Lambda 是如何引用单个方法的。 Java 编译器会尝试将引用的方法与第一个参数的类型匹配,使用第二个参数类型作为引用方法的参数。

引用实例方法

我们还也可以从 Lambda 定义中引用实例方法。首先,设想有如下接口

public interface Deserializer {
    public int deserialize(String v1);
}

该接口表示一个能够将字符串“反序列化”为 int 的组件。现在有一个 StringConvert 类

public class StringConverter {
    public int convertToInt(String v1){
        return Integer.valueOf(v1);
    }
}

        StringConvert 类 的 convertToInt() 方法与 Deserializer 接口的 deserialize() 方法具有相同的签名。因此,我们可以创建 StringConverter 的实例并从 Lambda 表达式中引用其 convertToInt() 方法,如下所示:

StringConverter stringConverter = new StringConverter();

Deserializer des = stringConverter::convertToInt;
// 等同于 Deserializer des = (value) -> stringConverter.convertToInt(value)

        上面第二行代码创建的 Lambda 表达式引用了在第一行创建的 StringConverter 实例的 convertToInt 方法。

引用构造方法

        最后如果 Lambda 的作用是调用一个类的构造方法,那么可以通过 Lambda 直接引用类的构造方法。在 Lambda 引用类构造方法的形式如下:

ClassName::new

那么如何将构造方法用作 lambda 表达式呢,假设我们有这样一个函数式接口

public interface Factory {
    public String create(char[] val);
}

Factory 接口的 create() 方法与 String 类中的其中一个构造方法的签名相匹配(String 类有多个重载版本的构造方法)。因此,String类的该构造方法也可以用作 Lambda 表达式。

Factory factory = String::new;
// 等同于 Factory factory (chars) -> String.new(chars);

总结

        今天这篇文章把 Lambda 表达式的知识梳理的了一遍,相信看完了这里的内容,再看到 Lambda 表达式的各种形态就不觉得迷惑了,虽然今天的文章看起来有点枯燥,不过是接下来 咱们系统学习 Stream 操作的基础,以及后面介绍 Java 中提供的几个函数式编程 interface 也会用到 Lambda 里的知识,后面的内容可以继续期待一下。

参考:

Java 8 (2/6篇) - Lambda表达式 & 函数式接口(FunctionalInterface Lib)_java8 lambda函数式接口_Jomurphys的博客-CSDN博客

彻底弄懂@FunctionalInterface、Lambda表达式和方法引用_@interface 表达式-CSDN博客

Java Lambda 表达式的各种形态和使用场景,看这篇就够了 - 知乎

你可能感兴趣的:(开发语言,java)