编译Lambda表达式:Scala与Java 8

在过去的几年中,Lambda表达式席卷了编程世界。 大多数现代语言都将它们用作函数式编程的基本部分。 基于JVM的语言(例如Scala,Groovy和Clojure)已将它们集成为该语言的关键部分。 现在,Java 8(最终)加入了这个乐趣。

拉姆达 Lambda表达式的有趣之处在于,从JVM的角度来看,它们是完全不可见的。 它不知道什么是匿名函数或Lambda表达式。 它只知道严格的OO规范字节码。 语言的创建者及其编译器应在这些约束条件下工作,以创建更新,更高级的语言元素。

我们在向Takipi添加Scala支持时必须首先深入到此过程,并且不得不深入研究Scala编译器。 Java 8即将来临,我认为看看Scala和Java编译器如何实现Lambda表达式会很有趣。 结果非常令人惊讶。

为了使事情顺利进行,我采用了一个简单的Lambda表达式,该表达式将字符串列表转换为长度列表。

在Java中–

List names = Arrays.asList("1", "2", "3");
Stream lengths = names.stream().map(name -> name.length());

在斯卡拉–

val names = List("1", "2", "3")
val lengths = names.map(name => name.length)

不要被它的简单性所欺骗-幕后发生了一些复杂的事情。

让我们从Scala开始

SCalaLam-1

编码

我使用javap来查看Scala编译器生成的.class的字节码内容。 让我们看一下结果字节码(这是JVM实际执行的)。

// this loads the names var into the stack (the JVM thinks
// of it as variable #2).
// It’s going to stay there for a while till it gets used
// by the <em>.map</em> function.

aload_2

接下来,事情变得更加有趣–由编译器生成的合成类的新实例被创建并初始化。 从JVM的角度来看,这是持有Lambda方法的对象。 有趣的是,虽然Lambda被定义为我们方法不可或缺的一部分,但实际上它完全不属于我们的课程。

new myLambdas/Lambda1$$anonfun$1 //instantiate the Lambda object
dup //put it into the stack again

// finally, invoke the c’tor. Remember - it’s just a plain object
// from the JVM’s perspective.
invokespecial myLambdas/Lambda1$$anonfun$1/()V

// these two (long) lines loads the immutable.List CanBuildFrom factory
// which will create the new list. This factory pattern is a part of
// Scala’s collections architecture
getstatic scala/collection/immutable/List$/MODULE$
Lscala/collection/immutable/List$;
invokevirtual scala/collection/immutable/List$/canBuildFrom()
Lscala/collection/generic/CanBuildFrom;

// Now we have on the stack the Lambda object and the factory.
// The next phase is to call the .<em>map</em>() function. 
// If you remember, we loaded the <em>names</em> var onto 
// the stack in the beginning. Now it’ll gets used as the 
// this for the .<em>map</em>() call, which will also 
// accept the Lambda object and the factory to produce the 
// new list of lengths.

invokevirtual scala/collection/immutable/List/map(Lscala/Function1;
Lscala/collection/generic/CanBuildFrom;)Ljava/lang/Object;

但是,请稍等-Lambda对象内部发生了什么?

Lambda对象

Lambda类派生自scala.runtime.AbstractFunction1 。 通过这个, map ()函数可以多态调用代码在下面的覆盖的apply()–

// this code loads this and the target object on which to act,
// checks that it’s a String, and then calls another apply overload
// to do the actual work and boxes its return value.
aload_0 //load this
aload_1 //load the string arg
checkcast java/lang/String //make sure it’s a String - we got an Object

// call another apply() method in the synthetic class
invokevirtual myLambdas/Lambda1$$anonfun$1/apply(Ljava/lang/String;)I

//box the result
invokestatic scala/runtime/BoxesRunTime/boxToInteger(I)Ljava/lang/Integer
areturn

执行的实际代码。 length ()操作嵌套在该附加的apply方法中,该方法只返回我们期望的String的长度。

ew ..到这里很长的路要走!

aload_1
invokevirtual java/lang/String/length()I
ireturn

对于像我们上面写的那么简单的一行,会生成很多字节码–一个额外的类和一堆新方法。 当然,这并不意味着要劝阻我们不要使用Lambda(我们在Scala中编写代码,而不是C语言)。 它只是说明了这些构造背后的复杂性。 只需考虑一下编译Lambda表达式的复杂链所需的代码量和复杂性!

我非常希望Java 8能够以相同的方式实现这一目标,但是很惊讶地看到他们完全采用了另一种方法。

Java 8 –一种新方法

JavaLam-1

这里的字节码短一些,但是却有些令人惊讶。 它很简单地通过加载名称 var并调用其开头stream ()方法,但随后它做得相当优雅。 它没有创建用于包装Lambda函数的新对象,而是使用Java 7中添加的新invokeDynamic指令将此调用站点动态链接到实际的Lambda函数。

aload_1 //load the names var

// call its stream() func
invokeinterface java/util/List.stream:()Ljava/util/stream/Stream;

//invokeDynamic magic!
invokedynamic #0:apply:()Ljava/util/function/Function;

//call the map() func
invokeinterface java/util/stream/Stream.map:
(Ljava/util/function/Function;)Ljava/util/stream/Stream;

调用动态魔术 。 在Java 7中添加了此JVM指令,以降低JVM的严格性,并允许动态语言在运行时绑定符号,而JVM编译代码时则静态地进行所有链接。

动态链接 。 如果您看一下实际的invokedynamic指令,您会看到没有实际的Lambda函数(称为lambda $ 0)的引用。 答案在于invokedynamic的设计方式(它本身应有完整的帖子),但是简短的答案是Lambda的名称和签名,在我们的案例中为–

// a function named lamda$0 that gets a String and returns an Integer
lambdas/Lambda1.lambda$0:(Ljava/lang/String;)Ljava/lang/Integer;

存储在.class单独表中的条目中,传递给指令的#0参数指向该表。 实际上,这个新表在几年之后第一次改变了字节码规范的结构,这要求我们也将Takipi的错误分析引擎也应用于它。

Lambda代码

这是实际Lambda表达式的代码。 这非常有用-只需加载String参数,调用length ()并将结果装箱。 请注意,它被编译为静态函数,以避免像我们在Scala中看到的那样将附加的对象传递给它。

aload_0
invokevirtual java/lang/String.length:()
invokestatic java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
areturn

这是invokedynamic方法的另一个优点,因为它允许我们从.map()函数的角度以多态的方式调用该方法,而不必分配包装对象或调用虚拟重写方法。 太酷了!

摘要

令人着迷的是,现代语言中最“严格”的Java现在如何使用动态链接为其新的Lambda表达式提供动力。 这也是一种有效的方法,因为不需要额外的类加载和编译-Lambda方法只是我们类中的另一个私有方法。

Java 8在使用Java 7中引入的新技术以非常简单的方式实现Lambda表达式方面确实做得非常出色。 令人高兴的是,即使像Java这样的“尊贵”女士也可以教给我们所有新技巧

参考: 编译Lambda表达式:来自Takipi博客的JCG合作伙伴 Tal Weiss的Scala vs. Java 8

翻译自: https://www.javacodegeeks.com/2014/01/compiling-lambda-expressions-scala-vs-java-8.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值