浅谈:Lambdas和invokedynamic

发布时间:2026/6/4 11:25:57

浅谈:Lambdas和invokedynamic 如大家所熟悉的Java语言和JVM工程师决定将翻译策略的选择推迟到运行时。Java 7 引入的新 invokedynamic 字节码指令为他们提供了一种高效实现这一目标的机制。将 lambda 表达式翻译为字节码分两步进行 生成一个被调用的动态调用站点称为 lambda 工厂该站点被调用时将返回一个功能接口实例而 lambda 将被转换为该功能接口 将 lambda 表达式的主体转换为一个方法该方法将通过 invokedynamic 指令调用。 为了说明第一步让我们检查一下编译包含 lambda 表达式的简单类时生成的字节码例如import java.util.function.Function; public class Lambda { FunctionString, Integer f s - Integer.parseInt(s); }这将转化为以下字节码0: aload_01: invokespecial #1 // Method java/lang/Object.:()V4: aload_05: invokedynamic #2, 0 // InvokeDynamic #0:apply:()Ljava/util/function/Function;10: putfield #3 // Field f:Ljava/util/function/Function;13: return请注意方法引用的编译方式略有不同因为 javac 不需要生成合成方法而是可以直接引用方法。 如何执行第二步取决于 lambda 表达式是非捕获lambda 不访问定义在其主体之外的任何变量还是捕获lambda 访问定义在其主体之外的变量。非捕获 lambda 只需将其反调为与 lambda 表达式签名完全相同的静态方法并在使用 lambda 表达式的同一类中声明即可。例如在上面的 lambda 类中声明的 lambda 表达式可以这样删减为一个方法static Integer lambda$1(String s) { return Integer.parseInt(s); }注意$1 并不是一个内部类它只是我们表示编译器生成代码的一种方式。 捕获 lambda 表达式的情况要复杂一些因为捕获的变量必须与 lambda 的形式参数一起传递给实现 lambda 表达式主体的方法。在这种情况下常见的翻译策略是在 lambda 表达式的参数前为每个捕获变量添加一个额外的参数。让我们来看一个实际例子int offset 100; FunctionString, Integer f s - Integer.parseInt(s) offset;相应方法的实现如下所示static Integer lambda$1(int offset, String s) { return Integer.parseInt(s) offset; }不过这种翻译策略并不是一成不变的因为 invokedynamic 指令的使用为编译器提供了灵活性使其可以在将来选择不同的实现策略。例如可以将捕获的值装入数组或者如果 lambda 表达式读取了使用该表达式的类的某些字段生成的方法可以是实例方法而不是静态方法这样就无需将这些字段作为额外参数传递。

相关新闻