目录
文章目录
- 目录
- 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
的入参类型肯定为Var1
和Var2
,于是我们继续省略:(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新引入的
Stream
和Optional
这两个类,让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 类中属于公共方法的抽象方法不会被视为单一抽象方法。
- 函数接口可以有默认方法和静态方法。
上述三原则引用自《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的外衣也就扒的差不多了,如果文中有什么表达或者理解错误的,欢迎指正。
推荐阅读
- Java|Java基础——数组
- 人工智能|干货!人体姿态估计与运动预测
- java简介|Java是什么(Java能用来干什么?)
- Java|规范的打印日志
- Linux|109 个实用 shell 脚本
- 程序员|【高级Java架构师系统学习】毕业一年萌新的Java大厂面经,最新整理
- Spring注解驱动第十讲--@Autowired使用
- SqlServer|sql server的UPDLOCK、HOLDLOCK试验
- jvm|【JVM】JVM08(java内存模型解析[JMM])
- 技术|为参加2021年蓝桥杯Java软件开发大学B组细心整理常见基础知识、搜索和常用算法解析例题(持续更新...)