lambda表达式的“脱糖”策略

招聘信息 2025-03-28 11:27:42 41

lambda表达式,语法比匿名类简单,可以替代匿名类,那么,lambda表达式到底是不是匿名类的语法糖;或者说lambda表达式编译之后,底层到底变成了什么;它和匿名类中的this关键字意义是否相同。

极客架构师——专注架构师成长。

大家好,我是码农老吴。

我们先简要回顾一下前面的代码,然后进行深入分析。

案例-商品过滤

类图

接口及类

SKU:商品实体类

ISKUService:商品服务接口

SKUService:商品服务实现类

IShopService:店铺服务接口

ShopService:店铺服务实现类

ShopServiceTest:店铺服务测试类

SKUService:商品服务实现类

定义了商品过滤的方法,使用了上期分享的JDK内置,函数式接口Predicate。这样我们就不用再定义商品过滤的策略接口。

ShopService:店铺服务实现类

在这个类中,除了searchSku01()使用匿名类实现商品过滤之外,后面三个方法,都是基于lambda表达式实现商品过滤的,之所以要三个方法,是因为三个方法中的lambda表达式,分别代表了一种特殊类型的lambda表达式。下面会详细讲解。

ShopServiceTest:店铺服务测试类

运行结果

Lambda表达式是否有独立的字节码文件

Java语言中的匿名类,出现的非常早,大概在版本就有了,大家一般都不陌生,众所周知,匿名类在编译之后,都会自动生成一个独立的字节码(class)文件。

文件名的格式是:外部类名$数字.class。

我们看看这个案例,它有几个独立的匿名类class文件。

从下图可以看到,编译器只生成了一个匿名类字节码文件,也就是ShopService$1.class.

反编译匿名类的独立字节码文件

从反编译的文件中,可以看到,ShopService$1.class这个文件确实是我们匿名类里面的内容。它执行了函数式接口PredicateSKU,实现了test方法。

而我们使用lambda表达式的代码,并没有生成对应的字节码文件。

这至少从表面上看,lambda表达式,底层并不是匿名类。

我们再看看使用匿名类的方法和使用lambda表达式的方法,各自对应的字节码文件,是否一样。

字节码文件对比

从下图可以看出,使用匿名类的searchSku01()方法,通过new指令,创建了匿名类shopService$1的对象,并通过invokespecial调用了对象的init()。

那么lambda表达式的底层到底是什么呢?

实际上,lambda表达式,确实是语法糖,但不是匿名类的语法糖,而是方法(它所在的类的)的语法糖,也就是说,lambda表达式,会被编译器先转义为,它所在的外部类的一个方法,根据lambda表达式的类型不同,可能是静态方法,也可能实例方法,编译器称这个操作为“脱糖”(desugar)操作,就好像小宝宝吃棒棒糖之前,需要剥掉糖衣一样。

方法怎么变多了

从下图,idea的字节码插件jclasslib中,可以看到,在shopService类中,虽然我们只定义了四个方法,但是它的字节码文件中,却多出了三个方法,这三个方法,就是编译器对我们的三个Lambda表达式“脱糖”之后产生的,两个静态方法,一个实例方法。

无状态(stateless)lambda表达式脱糖策略

以searchSku02()方法为例,这个表达式,没有访问外部类的任何局部变量或者属性,它是一个无状态的lambda表达式。它在脱糖之后,生成的就是静态方法,而且不需要传递额外的参数。

脱糖后字节码

从下图,可以看到,这个方法是私有静态方法(privatestatic),synthetic关键字表示这个方法,不是源代码中原生的方法(或者说程序员自己编写的方法),而是后期合成的,是编译器添加进去的。

伪代码

下面是searchSku02()方法,脱糖之后对应的伪代码,这个不是我瞎猜的哦,是参考了openJDK官方技术文档中提供的资料。因为searchSku02()是stateless的,所以生成的方法lambda$1是静态的。

@ServicepublicclassShopServiceimplementsIShopService{@OverridepublicListSKUsearchSku02(ListSKUsourceSkuList){(sourceSkuList,[lambdaforlambda$1asPredicate]);}privatestaticbooleanlambda$1(SKUsku){()10000}}
有状态(访问局部变量)lambda表达式脱糖策略

以searchSku03()方法为例,它访问了所在方法的局部变量miniStock,但是它并没有访问外部类的属性。它在脱糖之后,生成的也是是静态方法,但是方法的参数个数发生了变化。

脱糖后字节码

伪代码

注意,在这个生成的方法中lambda$1(intminiStock,SKUsku),局部变量miniStock需要作为方法参数,传入方法中。如果lambda表达式访问了多个局部变量,就需要相应的添加多个入参。

@ServicepublicclassShopServiceimplementsIShopService{@OverridepublicListSKUsearchSku03(ListSKUsourceSkuList){(sourceSkuList,[lambdaforlambda$1asPredicatecapturing(miniStock)]);}privatestaticbooleanlambda$1(intminiStock,SKUsku){()miniStock}}
有状态(访问外部类属性)lambda表达式脱糖策略

以searchSku04()方法为例,它访问了外部类的属性miniSales。它在脱糖之后,生成的方法,就不能是static方法了,而是实例方法。

脱糖后字节码

伪代码

注意,因为它访问了外部类的miniSales属性,生成的方法是实例方法,但是不用增加入参个数。

@ServicepublicclassShopServiceimplementsIShopService{@OverridepublicListSKUsearchSku03(ListSKUsourceSkuList){(sourceSkuList,INDY((MH(metaFactory),MH(),MH(invokeVirtual?.lambda$1))(this))));}privatebooleanlambda$1(SKUsku){()}}
this关键字的本质区别

通过上面对匿名类和lambda表达式字节码文件的分析,相信大家对二者中this关键字的区别,就了然于胸了。

运行结果

总结

lambda表达式是语法糖;

但它不是匿名类的语法糖,而是方法的语法糖。

匿名类中的this,是匿名类自身。

lambda表达式中的this,是它所在的外部类。

下期预告

那么对于lambda表达式与匿名类,双方的性能如何,我们在实际项目中该如何抉择呢,下期,我翻译解读一份由Oracle公司JDK开发人员,分享的一篇有关lambda表达式与匿名类的性能评比PPT文档,帮大家解答这个问题。

参考资料

《Java8inAction》

本文地址:https://zhpi.jsntrg.cn/224832070472.html
版权声明

本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。

全站热门

重生出嫁前一刻,这一世,不再轻信任何人,发展自己,守护家人

傅雷翻译奖颁出:极端分子制造恐怖,译者增进文明多元与理解

汇聚青春力量“学雷锋”我们在行动

2024年青岛市事业单位公开招聘863人,1月19日开始报名

盐城乒乓小将蒯曼“青公赛”再获金牌

牵手四大能源集团,青岛交运打造米图出行产业链

青岛交运“品牌集群”百花齐放,价值突破二百亿元

毕业生看过来 青岛人社局教你如何调档、申请补贴

友情链接

    备案号:京ICP备05032038号

    网站地图