java - 为什么使用 invokedynamic 调用 Java 8 lambda?

标签 java lambda jvm java-8 bytecode

invokedynamic 指令用于帮助虚拟机在运行时确定方法引用,而不是在编译时对其进行硬连接。

这对于动态语言很有用,在动态语言中,直到运行时才知道确切的方法和参数类型。但 Java lambda 的情况并非如此。它们被转换为具有明确定义的参数的静态方法。并且可以使用 invokestatic 调用此方法。

那么 lambda 需要 invokedynamic 吗,尤其是在性能受到影响的情况下?

最佳答案

Lambda 不是使用 invokedynamic 调用的,它们的对象表示是使用 invokedynamic 创建的,实际调用是常规 invokevirtualinvokeinterface

例如:

// 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"); 
}

任何 lambda 都必须成为某个基类或接口(interface)的实例。该实例有时包含从原始方法捕获的变量的副本,有时包含指向父对象的指针。 这可以作为匿名类实现。

为什么调用动态

简短的答案是:在运行时生成代码。

Java 维护者选择在运行时生成实现类。 这是通过调用 java.lang.invoke.LambdaMetafactory.metafactory 来完成的。 由于该调用的参数(返回类型、接口(interface)和捕获的参数)可能会更改,因此需要 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);
    }
);   

这需要在编译时创建类,然后在运行时加载。 jvm 的工作方式是,这些类将驻留在与原始类相同的目录中。第一次执行使用该 lambda 的语句时,必须加载并初始化该匿名类。

关于性能

第一次调用invokedynamic将触发匿名类生成。然后操作码 invokedynamic 被替换为 code这在性能上相当于手动编写匿名实例化。

关于java - 为什么使用 invokedynamic 调用 Java 8 lambda?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/41470740/

相关文章:

java - 在java中写入类路径内的文件

java - 无法解析方法 'buildAsync()'

c# - 如何使用 lambda 表达式创建扩展方法

C# 克隆包括 Lambda 表达式

java - 人们应该为 Scala 的 future 平台关注点做好准备?

java - 从 Java SDK 运行 Amazon EMR 作业

java - 如何禁用 JTree 上的默认键行为?

linq-to-sql - LINQ to SQL 用于排序和分页的扩展方法

java - 一个小程序让Windows 7彻底死机

java - 在 java 代码中设置 -noverify 标志