lambda表达式,语法比匿名类简单,可以替代匿名类,那么,lambda表达式到底是不是匿名类的语法糖;或者说lambda表达式编译之后,底层到底变成了什么;它和匿名类中的this关键字意义是否相同。
极客架构师——专注架构师成长。
大家好,我是码农老吴。
(相关资料图)
在前面的视频中,我们分享了策略模式的三次演变,从普通的策略模式,到匿名类的测试模式,再到lambda表达式的策略模式。可以看到,代码一次比一次简单。那么lambda表达式到底是不是匿名类的语法糖呢?这是个面试中,经常被提问的问题,我们需要认真对待。
我们先简要回顾一下前面的代码,然后进行深入分析。
类图
接口及类
SKU:商品实体类
ISKUService:商品服务接口
SKUService:商品服务实现类
IShopService:店铺服务接口
ShopService:店铺服务实现类
ShopServiceTest:店铺服务测试类
定义了商品过滤的方法,使用了上期分享的JDK内置,函数式接口Predicate。这样我们就不用再定义商品过滤的策略接口。
在这个类中,除了searchSku01()使用匿名类实现商品过滤之外,后面三个方法,都是基于lambda表达式实现商品过滤的,之所以要三个方法,是因为三个方法中的lambda表达式,分别代表了一种特殊类型的lambda表达式。下面会详细讲解。
Java语言中的匿名类,出现的非常早,大概在java1.1版本就有了,大家一般都不陌生,众所周知,匿名类在编译之后,都会自动生成一个独立的字节码(class)文件。
文件名的格式是:外部类名$数字.class。
我们看看这个案例,它有几个独立的匿名类class文件。
从下图可以看到,编译器只生成了一个匿名类字节码文件,也就是ShopService$1.class.
从反编译的文件中,可以看到,ShopService$1.class这个文件确实是我们匿名类里面的内容。它执行了函数式接口Predicate<SKU>,实现了test方法。
而我们使用lambda表达式的代码,并没有生成对应的字节码文件。
这至少从表面上看,lambda表达式,底层并不是匿名类。
我们再看看使用匿名类的方法和使用lambda表达式的方法,各自对应的字节码文件,是否一样。
从下图可以看出,使用匿名类的searchSku01()方法,通过new指令,创建了匿名类shopService$1的对象,并通过invokespecial调用了对象的init()。
而使用了lambda表达式的searchSku02()方法,则是通过invokedynamic指令,调用了lambda表达式。invokedynamic指令是JDK1.7引入的指令,用来支持动态语言在JVM中的执行。后面我根据大家的反馈,看看是否需要深入讲解这个指令。
那么lambda表达式的底层到底是什么呢?
实际上,lambda表达式,确实是语法糖,但不是匿名类的语法糖,而是方法(它所在的类的)的语法糖,也就是说,lambda表达式,会被编译器先转义为,它所在的外部类的一个方法,根据lambda表达式的类型不同,可能是静态方法,也可能实例方法,编译器称这个操作为“脱糖”(desugar)操作,就好像小宝宝吃棒棒糖之前,需要剥掉糖衣一样。
从下图,idea的字节码插件jclasslib中,可以看到,在shopService类中,虽然我们只定义了四个方法,但是它的字节码文件中,却多出了三个方法,这三个方法,就是编译器对我们的
三个Lambda表达式“脱糖”之后产生的,两个静态方法,一个实例方法。
以searchSku02()方法为例,这个表达式,没有访问外部类的任何局部变量或者属性,它是一个无状态的lambda表达式。它在脱糖之后,生成的就是静态方法,而且不需要传递额外的参数。
脱糖后字节码
从下图,可以看到,这个方法是私有静态方法(private static),synthetic关键字表示这个方法,不是源代码中原生的方法(或者说程序员自己编写的方法),而是后期合成的,是编译器添加进去的。
伪代码
下面是searchSku02()方法,脱糖之后对应的伪代码,这个不是我瞎猜的哦,是参考了openJDK官方技术文档中提供的资料。因为searchSku02()是stateless的,所以生成的方法lambda$1是静态的。
https://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html
以searchSku03()方法为例,它访问了所在方法的局部变量miniStock,但是它并没有访问外部类的属性。它在脱糖之后,生成的也是是静态方法,但是方法的参数个数发生了变化。
脱糖后字节码
伪代码
注意,在这个生成的方法中lambda$1(int miniStock,SKU sku),局部变量miniStock需要作为方法参数,传入方法中。如果lambda表达式访问了多个局部变量,就需要相应的添加多个入参。
以searchSku04()方法为例,它访问了外部类的属性miniSales。它在脱糖之后,生成的方法,就不能是static方法了,而是实例方法。
脱糖后字节码
伪代码
注意,因为它访问了外部类的miniSales属性,生成的方法是实例方法,但是不用增加入参个数。
通过上面对匿名类和lambda表达式字节码文件的分析,相信大家对二者中this关键字的区别,就了然于胸了。
运行结果
lambda表达式是语法糖;
但它不是匿名类的语法糖,而是方法的语法糖。
匿名类中的this,是匿名类自身。
lambda表达式中的this,是它所在的外部类。
那么对于lambda表达式与匿名类,双方的性能如何,我们在实际项目中该如何抉择呢,下期,我翻译解读一份由Oracle公司JDK开发人员,分享的一篇有关lambda表达式与匿名类的性能评比PPT文档,帮大家解答这个问题。
https://cr.openjdk.java.net/~briangoetz/lambda/lambda-translation.html
《Java8 in Action》
后期合成 ORACLE THIS PREDICATE CLASS TEST Java 了然于胸 IDEA 详细讲解 HTTPS 众所周知 也就是说 下期预告