我一直听说有很多新的很酷的特性被添加到 JVM 中,其中一个很酷的特性就是 invokedDynamic。我想知道它是什么,它是如何使 Java 中的反射编程更容易或更好的?
这是一个新的 JVM 指令,它允许编译器生成调用方法的代码,这些方法的规范比以前更宽松——如果您知道“ 鸭子打字”是什么,那么 invokedDynamic 基本上允许 Duck 类型化。作为一个 Java 程序员,您可以使用它做的事情不多; 但是,如果您是一个工具创建者,您可以使用它来构建更灵活、更有效的基于 JVM 的语言。给你是一篇非常贴心的博客文章,提供了很多细节。
不久前,C # 增加了一个很酷的特性,C # 中的动态语法
Object obj = ...; // no static type available dynamic duck = obj; duck.quack(); // or any method. no compiler checking.
可以把它看作是反射方法调用的语法糖。它可以有非常有趣的应用程序。参见 http://www.infoq.com/presentations/Statically-Dynamic-Typing-Neal-Gafter
Neal Gafter,负责 C # 的动态类型,刚刚从 SUN 叛变到 MS。所以认为 SUN 内部讨论过同样的事情并不是不合理的。
我记得在那之后不久,一些 Java 的家伙宣布了类似的东西
InvokeDynamic duck = obj; duck.quack();
不幸的是,在 Java7中找不到这个特性。非常失望。对于 Java 程序员来说,他们没有简单的方法在他们的程序中利用 invokedynamic。
invokedynamic
在继续调用 Dynamic 之前,有两个概念需要理解。
1. 静态与动态类型
静态 -预先在编译时进行类型检查(例如 Java)
Dynamic -预先在运行时进行类型检查(例如 JavaScript)
类型检查是一个验证程序是否为类型安全的过程,即检查类和实例变量、方法参数、返回值和其他变量的类型信息。 Java 知道 int,String,。.而 JavaScript 中对象的类型只能在运行时确定
2. 强打字与弱打字
Strong -指定对提供给其操作(例如 Java)的值类型的限制
弱 -如果操作的参数具有不兼容的类型(例如 VisualBasic) ,则转换(强制转换)这些参数
既然 Java 是静态和弱类型的,那么如何在 JVM 上实现动态和强类型语言呢?
InvokedDynamic 实现了一个运行时系统,该系统可以选择方法或函数的最合适的实现ーー在编译程序之后。
使用(a + b)并且在编译时对变量 a,b 一无所知,invokedDynamic 在运行时将这个操作映射到 Java 中最合适的方法。例如,如果 a,b 是字符串,那么调用方法(字符串 a,字符串 b)。如果 a,b 是 int,那么调用 method (int a,int b)。
InvokedDynamic 是用 Java7引入的。
作为我的 Java 唱片公司文章的一部分,我阐述了 InvokeDynamic 背后的动机。让我们从印第的粗略定义开始。
调用 Dynamic (也称为 印第)是 < a href = “ https://jcp.org/en/JSR/Details? id = 292”rel = “ noReferrer”> JSR 292 的一部分,目的是增强对 Dynamic Type Language 的 JVM 支持。在 Java7中首次发布之后,invokedynamic操作码及其 java.lang.invoke包被基于 JVM 的动态语言(如 JRuby)广泛使用。
java.lang.invoke
尽管 indy 专门设计来增强动态语言支持,但它提供的远不止这些。事实上,是 它适合用在语言设计者需要任何形式的动态性的地方,从动态类型的杂技到动态策略!
例如,爪哇8 Lambda 表达式实际上是使用 invokedynamic实现的,尽管 Java 是静态类型语言!
很长一段时间以来,JVM 确实支持四种方法调用类型: invokestatic调用静态方法、 invokeinterface调用接口方法、 invokespecial调用构造函数、 super()或私有方法以及 invokevirtual调用实例方法。
invokestatic
invokeinterface
invokespecial
super()
invokevirtual
尽管存在差异,但这些调用类型有一个共同的特征: 我们不能用自己的逻辑来丰富它们
当 JVM 第一次看到 invokedynamic指令时,它调用一个名为 自助法的特殊静态方法。这个自助法是一段 Java 代码,我们编写这段代码是为了准备实际的被调用逻辑:
然后自助法返回一个 java.lang.invoke.CallSite的实例,这个 CallSite保存着对实际方法的引用,也就是 MethodHandle。
java.lang.invoke.CallSite
CallSite
MethodHandle
从现在开始,每当 JVM 再次看到这个 invokedynamic指令时,它都会跳过 慢路,直接调用底层的可执行文件。JVM 继续跳过缓慢的路径,除非有什么变化。
Java14Records提供了一种很好的紧凑语法来声明应该是哑数据持有者的类。
Records
考虑到这个简单的记录:
public record Range(int min, int max) {}
这个例子的字节码是这样的:
Compiled from "Range.java" public java.lang.String toString(); descriptor: ()Ljava/lang/String; flags: (0x0001) ACC_PUBLIC Code: stack=1, locals=1, args_size=1 0: aload_0 1: invokedynamic #18, 0 // InvokeDynamic #0:toString:(LRange;)Ljava/lang/String; 6: areturn
在 自助法中:
BootstrapMethods: 0: #41 REF_invokeStatic java/lang/runtime/ObjectMethods.bootstrap: (Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String; Ljava/lang/invoke/TypeDescriptor;Ljava/lang/Class; Ljava/lang/String;[Ljava/lang/invoke/MethodHandle;)Ljava/lang/Object; Method arguments: #8 Range #48 min;max #50 REF_getField Range.min:I #51 REF_getField Range.max:I
因此,Records 的 鞋带方法称为 bootstrap,它驻留在 java.lang.runtime.ObjectMethods类中。正如你所看到的,这个自助法需要以下参数:
bootstrap
java.lang.runtime.ObjectMethods
MethodHandles.Lookup
Ljava/lang/invoke/MethodHandles$Lookup
toString
equals
hashCode
ConstantCallSite
TypeDescriptor
Ljava/lang/invoke/TypeDescriptor
Class<?>
Class<Range>
min;max
invokedynamic指令将所有这些参数传递给自助法。自助法依次返回一个 ConstantCallSite的实例。这个 ConstantCallSite持有对请求的方法实现的引用,例如 toString。
与反射 API 相反,java.lang.invoke API 非常有效,因为 JVM 可以完全看穿所有调用。因此,JVM 可以应用各种各样的优化,只要我们尽可能避免缓慢的路径!
除了效率的论据,invokedynamic方法更可靠,因为 它的简单的脆性较小。
此外,为 JavaRecords 生成的字节码与属性的数量无关。因此,更少的字节码和更快的启动时间。
最后,让我们假设一个新版本的 Java 包含一个新的、更有效的自助法实现。使用 invokedynamic,我们的应用程序可以在不重新编译的情况下利用这一改进。这样我们就有了某种 前向二进制兼容性。而且,这就是我们所说的动态策略!
除了 Java Records,调用 Dynamic还被用来实现以下特性:
LambdaMetafactory
StringConcatFactory
简短的回答是 invokedDynamic 是 JVM 中的一个新操作码,在 JAVA7之前不存在。
至于反射,在这个定义的上下文中: Java 反射是在运行时检查或修改类的运行时行为的过程。,但是,我认为需要更多的解释。 从 文章以下:
例如,反射早于集合和泛型 结果,方法签名由反射中的 Class []表示 这可能会非常麻烦和容易出错,并且会受到 Java 的数组语法冗长的性质 需要手动装箱和取消装箱的基本类型,并围绕 无效方法的可能性。 方法句柄 而不是强迫程序员处理 针对这些问题,Java7引入了一个新的 API,称为 MethodHandles, 表示必要的抽象。这个 API 的核心是 调用,特别是类 MethodHandle。 此类型的实例提供调用方法的能力,并且它们 是直接可执行的 参数和返回类型,它提供的类型安全性与 考虑到使用它们的动态方式,这些 API 是可能的 也可以单独使用,在这种情况下 它可以被认为是现代的、安全的反思替代品
例如,反射早于集合和泛型 结果,方法签名由反射中的 Class []表示 这可能会非常麻烦和容易出错,并且会受到 Java 的数组语法冗长的性质 需要手动装箱和取消装箱的基本类型,并围绕 无效方法的可能性。
方法句柄
而不是强迫程序员处理 针对这些问题,Java7引入了一个新的 API,称为 MethodHandles, 表示必要的抽象。这个 API 的核心是 调用,特别是类 MethodHandle。 此类型的实例提供调用方法的能力,并且它们 是直接可执行的 参数和返回类型,它提供的类型安全性与 考虑到使用它们的动态方式,这些 API 是可能的 也可以单独使用,在这种情况下 它可以被认为是现代的、安全的反思替代品
引自 使用 invokedDynamic 理解 Java 方法调用
这四个字节码是 在 Java8和 Java9中使用的方法调用,它们是 Invokevirtal、 invokspecial、 invokeinterface 和 invokestatic 。 这就提出了一个问题,第五个操作码,invokedDynamic, 简短的回答是,在 Java9中,有 Java 语言中没有对 invokedDynamic 的直接支持。 实际上,当在 Java7中将 invokedDynamic 添加到运行时时, Javac 编译器在任何情况下都不会发出新的字节码 无论如何。 从 Java8开始,invokedDynamic 被用作主要的实现 机制,以提供先进的平台功能 这种操作码使用的最简单的例子在 Lambda 表达式的实现。
这四个字节码是 在 Java8和 Java9中使用的方法调用,它们是 Invokevirtal、 invokspecial、 invokeinterface 和 invokestatic 。
这就提出了一个问题,第五个操作码,invokedDynamic, 简短的回答是,在 Java9中,有 Java 语言中没有对 invokedDynamic 的直接支持。
实际上,当在 Java7中将 invokedDynamic 添加到运行时时, Javac 编译器在任何情况下都不会发出新的字节码 无论如何。
从 Java8开始,invokedDynamic 被用作主要的实现 机制,以提供先进的平台功能 这种操作码使用的最简单的例子在 Lambda 表达式的实现。
因此,invokedDynamic 是一个新的操作码,它允许在 JAVA 中使用一个新的对象引用类型,即 Lambda。