我一直听说有关添加到JVM的所有新酷功能,其中一个很酷的功能是invokedynamic。我想知道它是什么以及它如何使Java中的反射编程更容易或更好?
答案 0 :(得分:151)
这是一个新的JVM指令,它允许编译器生成调用方法的代码,这些代码调用的规范比以前更宽松 - 如果你知道“duck typing”是什么,invokedynamic基本上允许鸭子输入。作为Java程序员可以用它做的事情并不多;但是,如果您是工具创建者,则可以使用它来构建更灵活,更高效的基于JVM的语言。 Here是一篇非常精彩的博文,提供了很多细节。
答案 1 :(得分:8)
前段时间,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
负责C#动态类型的Neal Gafter刚刚从SUN叛逃到MS。因此,认为在SUN内部讨论过同样的事情并不是没有道理的。
我记得不久之后,一些Java家伙宣布了类似的东西
InvokeDynamic duck = obj;
duck.quack();
不幸的是,该功能无法在Java 7中找到。非常失望。对于Java程序员来说,他们没有简单的方法可以在他们的程序中利用invokedynamic
。
答案 2 :(得分:7)
在我的Java Records文章中,我阐述了Inoke Dynamic背后的动机。让我们从Indy的粗略定义开始。
调用动态(也称为 Indy )是 JSR 292 的一部分,旨在增强对动态类型语言的JVM支持。在Java 7中首次发布后,invokedynamic
操作码及其java.lang.invoke
包被基于动态JVM的语言(如JRuby)广泛使用。
尽管indy专为增强动态语言支持而设计,但它提供的功能远不止这些。实际上,适合在语言设计人员需要任何形式的动态的地方使用,从动态类型的杂技到动态策略!
例如,即使Java是静态类型的语言,Java 8 Lambda表达式实际上是使用invokedynamic
实现的!
一段时间以来,JVM确实支持四种方法调用类型:invokestatic
调用静态方法,invokeinterface
调用接口方法,invokespecial
调用构造函数,super()
或私有方法和invokevirtual
来调用实例方法。
尽管它们之间存在差异,但这些调用类型具有一个共同的特征:我们无法用自己的逻辑来丰富它们。相反,invokedynamic
使我们能够以所需的任何方式引导调用过程。然后,JVM将负责直接调用Bootstrapped方法。
JVM第一次看到一条invokedynamic
指令时,会调用一个称为 Bootstrap Method 的特殊静态方法。 bootstrap方法是一段我们编写的Java代码,用于准备实际的调用逻辑:
然后bootstrap方法返回java.lang.invoke.CallSite
的实例。 CallSite
包含对实际方法的引用,即MethodHandle
。
从现在开始,每次JVM再次看到此invokedynamic
指令时,它都会跳过 Slow Path 并直接调用底层可执行文件。除非有所更改,否则JVM会继续跳过慢速路径。
Java 14 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方法称为bootstrap
,它位于java.lang.runtime.ObjectMethods
类中。如您所见,此引导程序方法需要以下参数:
MethodHandles.Lookup
实例
(Ljava/lang/invoke/MethodHandles$Lookup
部分)。toString
,equals
,hashCode
等)
将要链接。例如,当值为toString
时,引导程序
将返回一个ConstantCallSite
(一个不变的CallSite
),
指向为此特定的实际toString
实现
记录。TypeDescriptor
的{{1}}
部分)。Ljava/lang/invoke/TypeDescriptor
,代表Record类的类型。它的
Class<?>
。Class<Range>
)之间用分号分隔的列表。 min;max
。这样,引导方法可以
为此组件创建一个MethodHandle
方法实现。 MethodHandle
指令将所有这些参数传递给bootstrap方法。引导方法又返回invokedynamic
的实例。 ConstantCallSite
保留了对请求的方法实现的引用,例如ConstantCallSite
。
与反射API相比,toString
API的效率很高,因为JVM可以完全查看所有调用。因此,只要我们尽可能避免慢路径,JVM可以应用各种优化!
除了效率论点之外,由于its simplicity,java.lang.invoke
方法更可靠,更不易碎。
此外,为Java记录生成的字节码与属性的数量无关。因此,更少的字节码和更快的启动时间。
最后,让我们假设Java的新版本包含新的且更有效的bootstrap方法实现。借助invokedynamic
,我们的应用无需重新编译即可利用此改进。这样,我们就有了某种 Forward Binary Compatibility 。另外,这就是我们正在谈论的动态策略!
除了Java记录外,调用动态还用于实现以下功能:
LambdaMetafactory
StringConcatFactory
答案 3 :(得分:2)
在继续调用动力学之前,有两个概念需要理解。
1。静态打字与动态打字
静态-在编译时执行类型检查(例如Java)
动态-在运行时执行类型检查(例如JavaScript)
类型检查是一种验证程序是否为类型安全的过程,即检查类和实例变量,方法参数,返回值和其他变量的类型信息。 例如。 Java在编译时就知道int,String等,而JavaScript中的对象类型只能在运行时确定
2。强打字与弱打字
强-指定对其操作提供的值类型(例如Java)的限制
弱-如果操作的参数具有不兼容的类型(例如,Visual Basic),则将其转换(转换)
知道Java是静态类型和弱类型的语言,您如何在JVM上实现动态类型和强类型的语言?
invokedynamic实现了一个运行时系统,该系统可以在程序编译后选择最合适的方法或函数实现。
示例: 拥有(a + b)并且在编译时不了解变量a,b的任何信息,invokedynamic会在运行时将此操作映射到Java中最合适的方法。例如,如果结果a,b是字符串,则调用method(String a,String b)。如果发现a,b是整数,则调用method(int a,int b)。
invokedynamic是Java 7引入的。