Dan*_*rry 69
Lambda不会被调用invokedynamic
,它们的对象表示是使用创建的invokedynamic
,实际的调用是常规的invokevirtual
或invokeinterface
.
例如:
// creates an instance of (a subclass of) Consumer
// with invokedynamic to java.lang.invoke.LambdaMetafactory
something(x -> System.out.println(x));
void something(Consumer<String> consumer) {
// invokeinterface
consumer.accept("hello");
}
Run Code Online (Sandbox Code Playgroud)
任何lambda都必须成为某个基类或接口的实例.该实例有时包含从原始方法捕获的变量的副本,有时包含指向父对象的指针.这可以作为匿名类实现.
为什么invokedynamic
简短的回答是:在运行时生成代码.
Java维护者选择在运行时生成实现类.这是通过调用完成的java.lang.invoke.LambdaMetafactory.metafactory
.由于该调用的参数(返回类型,接口和捕获的参数)可以更改,因此需要invokedynamic
.
使用invokedynamic
构建在运行时的匿名类,允许JVM生成运行时类的字节码.对同一语句的后续调用使用缓存版本.使用的另一个原因invokedynamic
是能够在将来更改实现策略,而无需更改已编译的代码.
没有走的路
另一个选项是编译器为每个lambda实例创建一个内部类,相当于将上面的代码转换为:
something(new Consumer() {
public void accept(x) {
// call to a generated method in the base class
ImplementingClass.this.lambda$1(x);
// or repeating the code (awful as it would require generating accesors):
System.out.println(x);
}
);
Run Code Online (Sandbox Code Playgroud)
这需要在编译时创建类,然后在运行时加载.jvm运行这些类的方式将与原始类位于同一目录中.并且第一次执行使用该lambda的语句时,必须加载并初始化该匿名类.
关于表现
第一次调用invokedynamic
将触发匿名类生成.然后将操作码invokedynamic
替换为与手动写入匿名实例化的性能等效的代码.
Edw*_*rzo 44
Brain Goetz在他的一篇论文中解释了lambda翻译策略的原因,遗憾的是现在这些论文似乎不可用.幸运的是我保留了一份副本:
翻译策略
我们可以通过多种方式在字节码中表示lambda表达式,例如内部类,方法句柄,动态代理等.这些方法中的每一种都有利有弊.在选择策略时,有两个相互竞争的目标:通过不承诺特定策略来最大化未来优化的灵活性,而不是在类文件表示中提供稳定性.我们可以通过使用JSR 292中的invokedynamic功能将字节码中的lambda创建的二进制表示与运行时评估lambda表达式的机制分开来实现这两个目标.我们不是生成字节码来创建实现lambda表达式的对象(例如调用内部类的构造函数),而是描述构造lambda的配方,并将实际构造委托给语言运行库.该配方在invokedynamic指令的静态和动态参数列表中进行编码.
使用invokedynamic可以让我们将转换策略的选择推迟到运行时.运行时实现可以自由选择策略来评估lambda表达式.运行时实现选项隐藏在用于lambda构造的标准化(即平台规范的一部分)API之后,因此静态编译器可以发出对此API的调用,并且JRE实现可以选择其首选实现策略.invokedynamic机制允许这样做,而没有这种后期绑定方法可能带来的性能成本.
当编译器遇到lambda表达式时,它首先将lambda体降低(desugars)为一个方法,其参数列表和返回类型与lambda表达式匹配,可能还有一些额外的参数(对于从词法范围捕获的值,如果有的话). )在捕获lambda表达式的点上,它生成一个invokedynamic调用站点,当调用该站点时,返回lambda正在转换的函数接口的实例.该调用站点称为给定lambda的lambda工厂.lambda工厂的动态参数是从词法范围捕获的值.lambda工厂的bootstrap方法是Java语言运行时库中的标准化方法,称为lambda metafactory.静态引导参数在编译时捕获有关lambda的信息(它将被转换的功能接口,desugared lambda主体的方法句柄,有关SAM类型是否可序列化的信息等)
方法引用的处理方式与lambda表达式相同,只是大多数方法引用不需要被置于新方法中; 我们可以简单地为引用的方法加载一个常量方法句柄并将其传递给metafactory.
所以,这里的想法似乎是封装翻译策略,而不是通过隐藏这些细节来提交一种特定的做事方式.在未来,当类型擦除和缺少值类型已经解决并且Java可能支持实际的函数类型时,它们可能也会去那里并将该策略更改为另一个策略而不会在用户代码中引起任何问题.
小智 5
当前Java 8的lambda实现是一个复合决策:
因此,为了回答您的问题,
归档时间: |
|
查看次数: |
13565 次 |
最近记录: |