Lambda表达式与函数式接口

目录
文章目录

  • 目录
  • Lambda表达式Java函数式接口
    • 简述Lambda表达式
    • Java中Lambda表达式的好处
    • Java中Lambda的实现原理
      • Java1.8引入的新函数式接口
      • Java编译器自动优化实现函数接口
      • Java编译器对动态函数的优化

Lambda表达式Java函数式接口 都9012年了,这篇博文代码看看5000年前4012年发布的Java 8新引入的Lambda表达式到底是个什么鬼。
简述Lambda表达式 Lambda表达式并不是Java 8特有的特性,其设计初衷是用于一些特定代码中,已知固定入参和固定返回值的时候,动态生成的一种函数。
举个栗子:
public Var3 func(Var1 var1, Var2 var2) { Var3 var3 = doSomeThing(var1, var2); return var3; }

上述代码是我们常见的Java代码格式,假如说我们已经设定这个函数只会被在特定位置被调用,或者换种说法,我们假设func函数仅仅只会作为func2(Var3 var3)的入参。同时如果我们把func这个函数的声明放到func2的入参时声明,那此时我们此时调用时,其实这个函数是叫func还是叫funcA还是叫ABCD已经无所谓了。
因此我们就可以省略这个函数名,将其替换成->,由此将上述函数省略为:
Var3 (Var1 var1, Var2 var2) -> { Var3 var3 = doSomeThing(var1, var2); return var3; }

接下来由于我们已知func2(Var3 var3)的入参肯定为Var3类型,所以上述代码又可以进一步省略:
(Var1 var1, Var2 var2) -> { Var3 var3 = doSomeThing(var1, var2); return var3; }

同理由于我们已知func的入参类型肯定为Var1Var2,于是我们继续省略:
(var1, var2) -> { Var3 var3 = doSomeThing(var1, var2); return var3; }

然后由于我们如果整个函数内部只有一行操作的话,则可以知道这个操作的返回值肯定是这一行的操作结果,因此我们继续省略:
(var1, var2) -> doSomeThing(var1, var2);

最后,假如我们初始函数是:
Var3 (Var1 var1) -> { return var1.doSomeThing(); }

在我们简写成上述常见的表达式之后:
var1 -> var1.doSomeThing();

由于我们知道入参只有一个,并且操作就是调用这个参数的一个子方法,而且这个入参叫做var1还是ABCD都无所谓,这个时候我们就可以极致缩写:
Var1::doSomeThing()

意味着对这个表达式的入参直接调用Var1类的doSomeThing,然后将结果返回。
到此,整个Lambda写法的产生原因我们就已经知道了,不理解的可以重新回看整个缩写过程。
注意这里的推理过程是所有支持Lambda表达式的开发语言通用的精简思路,之后切换语言遇到Lambda表达式,我们需要用同样的思路面对。
实不相瞒,上述简化过程我就是在Python教程中知道的。
Java中Lambda表达式的好处 在我们Java 8之前,我们可能暂时还没有体会到Lambda表达式的好处,但是Java 8新引入的StreamOptional这两个类,让Java 8引入Lambda表达式成为一种趋势。
继续举个栗子,假如我们现在有一个多层级的对象,我们需要获取其最底层的一个字段时,使用Optional类可以比较方便的判定,相关教程见我另一个帖子《Optional工具类》。
这里我们先不要发散,假如我们使用Optional取一个中间可能存在null的多层级对象时,假设我们现在还不知道Lambda表达式这个东西,而是单纯使用Optional所有方法提供的入参直接暴力实现,那么最后的代码如下:
package com.main; import org.junit.Test; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; public class MainFunc {public class Test1 { private Test2 test2; public Test2 getTest2() { return test2; }public void setTest2(Test2 test2) { this.test2 = test2; } }public class Test2 { private List test3; public List getTest3() { return test3; }public void setTest3(List test3) { this.test3 = test3; } }public class Test3 { private String str; public String getStr() { return str; }public void setStr(String str) { this.str = str; } }@Test public void test4() { Test1 test1 = new Test1(); Test2 test2 = new Test2(); test1.setTest2(test2); List list = new ArrayList<>(); list.add(new Test3()); test2.setTest3(list); String str = "123"; str.concat("456"); list.get(0).setStr(str); Optional> opt = Optional.ofNullable(test1).map(new Function() { @Override public Test2 apply(Test1 test1) { return test1.getTest2(); } }).map(new Function>() { @Override public List apply(Test2 test2) { return test2.getTest3(); } }).map(new Function, Test3>() { @Override public Test3 apply(List list) { return list != null && list.size() > 0 ? list.get(0) : null; } }).map(new Function() { @Override public String apply(Test3 test3) { return test3.getStr(); } }); if (opt.isPresent()) { System.out.println(opt.get()); } } }

整段代码中,由于我们按照Optional.map的要求,通过实现Fuction接口函数,并且重写其apply函数,从而实现业务诉求。但是这种实现方式阅读费力,并且撰写辛苦,而所有map中实质的操作却仅仅只有一行,这种代码既不优雅,也不是我们引入Optional的初衷。
在这种情况下,我们使用Lambda表达式替代上述代码中test4的实现:
@Test public void test4() { Test1 test1 = new Test1(); Test2 test2 = new Test2(); test1.setTest2(test2); List list = new ArrayList<>(); list.add(new Test3()); test2.setTest3(list); String str = "123"; str.concat("456"); list.get(0).setStr(str); if (opt.isPresent()) { System.out.println(opt.get()); } Optional> opt = Optional.ofNullable(test1) .map(t1 -> t1.getTest2()) .map(t2 -> t2.getTest3()) .map(t3List -> t3List != null && t3List.size() > 0 ? t3List.get(0) : null) .map(t3 -> t3.getStr()); if (opt.isPresent()) { System.out.println(opt.get()); } }

最后是极致简写方法:
@Test public void test4() { Test1 test1 = new Test1(); Test2 test2 = new Test2(); test1.setTest2(test2); List list = new ArrayList<>(); list.add(new Test3()); test2.setTest3(list); String str = "123"; str.concat("456"); list.get(0).setStr(str); if (opt.isPresent()) { System.out.println(opt.get()); } Optional> opt = Optional.ofNullable(test1) .map(Test1::getTest2) .map(Test2::getTest3) .map(test3 -> test3 != null && test3.size() > 0 ? test3.get(0) : null) .map(Test3::getStr); if (opt.isPresent()) { System.out.println(opt.get()); } }

整个实现上瞬间清爽很多,并且代码量非常少。
Java中Lambda的实现原理 其实在上述代码优化过程中,从最开始直接在map方法中实现函数接口,到直接替换成Lambda表达式,我们省略了一个推导步骤:
首先尝试将函数接口的实现抽出去:
@Test public void test4() { Test1 test1 = new Test1(); Test2 test2 = new Test2(); test1.setTest2(test2); List list = new ArrayList<>(); list.add(new Test3()); test2.setTest3(list); String str = "123"; str.concat("456"); list.get(0).setStr(str); Function func1 = new Function() { @Override public Test2 apply(Test1 test1) { return test1.getTest2(); } }; Function> func2 = new Function>() { @Override public List apply(Test2 test2) { return test2.getTest3(); } }; Function, Test3> func3 = new Function, Test3>() { @Override public Test3 apply(List list) { return list != null && list.size() > 0 ? list.get(0) : null; } }; Function func4 = new Function() { @Override public String apply(Test3 test3) { return test3.getStr(); } }; Optional> opt = Optional.ofNullable(test1).map(func1).map(func2).map(func3).map(func4); if (opt.isPresent()) { System.out.println(opt.get()); } }

然后我们有已经知道map中实际是可以直接填Lambda表达式的,这里我们尝试将Lambda表达式赋值给func1、func2、func3、func4:
@Test public void test4() { Test1 test1 = new Test1(); Test2 test2 = new Test2(); test1.setTest2(test2); List list = new ArrayList<>(); list.add(new Test3()); test2.setTest3(list); String str = "123"; str.concat("456"); list.get(0).setStr(str); Function func1 = Test1::getTest2; Function> func2 = Test2::getTest3; Function, Test3> func3 = (l) -> l != null && l.size() > 0 ? l.get(0) : null; Function func4 = Test3::getStr; Optional> opt = Optional.ofNullable(test1).map(func1).map(func2).map(func3).map(func4); if (opt.isPresent()) { System.out.println(opt.get()); } }

OK,到这里,我们可以发现发现所谓Lambda表达式,实际上就是自己帮你实现了一个函数式接口而已,这部分实现过程由Java 8之前你来完成,优化到了编译器自己完成,从而实现了代码上的优雅。
这里我们引申一下,在Java中,函数接口有 3 条重要法则:
  • 一个函数接口只有一个抽象方法。
  • 在 Object 类中属于公共方法的抽象方法不会被视为单一抽象方法。
  • 函数接口可以有默认方法和静态方法。
任何满足单一抽象方法法则的接口,都会被自动视为函数接口。这包括 Runnable 和 Callable 等传统接口,以及您自己构建的自定义接口。
上述三原则引用自《Java 8 习惯用语,第 7 部分 —— 函数接口》
接下来,让我开始尝试看看Java编译器(JDK)再给我们编译Lambda表达式时做了哪些优化动作。
Java1.8引入的新函数式接口
所有函数式接口见java.util.function包,这里只挑取几个典型的。
Consumer接口:提供一个void accept(T t); 函数,一般我们的函数只有一个入参,没有返回值时,可以实现该接口
Function接口:提供一个R apply(T t); 函数,一般我们只有一个入参,同时有返回值时,可以实现该接口,标准的最常用的函数式接口
Predicate接口:提供一个boolean test(T t); 函数,一般我们需要对入参做一些判断时,可以实现该接口,Stream.filter的入参就是该接口的实现类。
Supplier接口:提供一个T get(); 函数,如果函数没有入参,只有返回值,譬如我们的JavaBean中的get方法,可以实现该接口。
Java编译器自动优化实现函数接口
由上我们已知,其实Java 8中带来的Lambda表达式,就是一种能够减少我们实现接口函数的语法糖,Java能够通过我们的返回值,讲一个Lambda表达式合理的转换成一个函数接口的实现。
在了解其本质之后,我们甚至可以自己定义一个接口函数用于接收一个Lambda表达式:
package com.main; import org.junit.Test; public class MainFunc {@FunctionalInterface private interface FI { void run(N n); }; private void showFi(FI> n) { n.run("showFi"); }@Test public void test4() { showFi(s -> System.out.println(s)); } }

java编译器的实现是一种动态实现,不受函数接口的接口名或者其抽象方法的名称的限制,由此我们也说java中的Lambda表达式是一种动态语言类型。
Java编译器对动态函数的优化
如果我们希望一窥JDK8在编译过程中,如何实现通过阅读反编译之后的class的代码进行查阅,指令为javap -v -p YourClass.class > yourRecordFile,整个函数的编译结果如下:
public void test4(); descriptor: ()V flags: ACC_PUBLIC RuntimeVisibleAnnotations: 0: #16() Code: stack=4, locals=6, args_size=1 0: new #17 // class com/main/MainFunc$Test1 3: dup 4: aload_0 5: invokespecial #19 // Method com/main/MainFunc$Test1."":(Lcom/main/MainFunc; )V 8: astore_1 9: new #22 // class com/main/MainFunc$Test2 12: dup 13: aload_0 14: invokespecial #24 // Method com/main/MainFunc$Test2."":(Lcom/main/MainFunc; )V 17: astore_2 18: aload_1 19: aload_2 20: invokevirtual #25 // Method com/main/MainFunc$Test1.setTest2:(Lcom/main/MainFunc$Test2; )V 23: new #29 // class java/util/ArrayList 26: dup 27: invokespecial #31 // Method java/util/ArrayList."":()V 30: astore_3 31: aload_3 32: new #32 // class com/main/MainFunc$Test3 35: dup 36: aload_0 37: invokespecial #34 // Method com/main/MainFunc$Test3."":(Lcom/main/MainFunc; )V 40: invokeinterface #35, 2 // InterfaceMethod java/util/List.add:(Ljava/lang/Object; )Z 45: pop 46: aload_2 47: aload_3 48: invokevirtual #41 // Method com/main/MainFunc$Test2.setTest3:(Ljava/util/List; )V 51: ldc #45 // String 123 53: astore 4 55: aload 4 57: ldc #47 // String 456 59: invokevirtual #49 // Method java/lang/String.concat:(Ljava/lang/String; )Ljava/lang/String; 62: pop 63: aload_3 64: iconst_0 65: invokeinterface #55, 2 // InterfaceMethod java/util/List.get:(I)Ljava/lang/Object; 70: checkcast #32 // class com/main/MainFunc$Test3 73: aload 4 75: invokevirtual #59 // Method com/main/MainFunc$Test3.setStr:(Ljava/lang/String; )V 78: aload_1 79: invokestatic #63 // Method java/util/Optional.ofNullable:(Ljava/lang/Object; )Ljava/util/Optional; 82: invokedynamic #72, 0 // InvokeDynamic #0:apply:()Ljava/util/function/Function; 87: invokevirtual #73 // Method java/util/Optional.map:(Ljava/util/function/Function; )Ljava/util/Optional; 90: invokedynamic #77, 0 // InvokeDynamic #1:apply:()Ljava/util/function/Function; 95: invokevirtual #73 // Method java/util/Optional.map:(Ljava/util/function/Function; )Ljava/util/Optional; 98: invokedynamic #78, 0 // InvokeDynamic #2:apply:()Ljava/util/function/Function; 103: invokevirtual #73 // Method java/util/Optional.map:(Ljava/util/function/Function; )Ljava/util/Optional; 106: invokedynamic #79, 0 // InvokeDynamic #3:apply:()Ljava/util/function/Function; 111: invokevirtual #73 // Method java/util/Optional.map:(Ljava/util/function/Function; )Ljava/util/Optional; 114: astore 5 116: aload 5 118: invokevirtual #80 // Method java/util/Optional.isPresent:()Z 121: ifeq 138 124: getstatic #84 // Field java/lang/System.out:Ljava/io/PrintStream; 127: aload 5 129: invokevirtual #90 // Method java/util/Optional.get:()Ljava/lang/Object; 132: checkcast #50 // class java/lang/String 135: invokevirtual #93 // Method java/io/PrintStream.println:(Ljava/lang/String; )V 138: return

此时我们就不难发现,其实这里我们的4个lambda表达式编译结果对应的字节码指令为invokedynamic,这也就意味着在我们只有将lambda表达式的返回值赋值给一个函数接口的时候,他的类型才能够给动态识别,由此实现了lambda表达式的动态绑定。
【Lambda表达式与函数式接口】由此lambda的外衣也就扒的差不多了,如果文中有什么表达或者理解错误的,欢迎指正。

    推荐阅读