我有点高兴地偶然发现Constable了 JDK 15 之类的东西。我基本明白了。
在翻遍了所有编译器理论甚至理解了一点之后,我发现我仍然有一个问题:谁调用 aConstable的describeConstable()方法,何时调用? Brian 的演示似乎暗示它在编译时以某种方式被访问。对这些事情太天真了,我期待它出现在使用页面下jdk.compiler或其他东西中。相反,唯一的消耗似乎是在jdk.incubator.foreign包中。(显然,我知道它可能会被使用页面未公开的某些私人机器使用;因此我的问题。)
我放在Thread.dumpStack()一个describeConstable()哑类的实现,实现Constable和回报Optional.ofNullable(null)只是为了看看会发生什么,......什么都没有发生在编译期或运行时。
(我确实知道,在 JDK 12 之前,如果您想编写动态常量,您必须使用 ASM 或 ByteBuddy 或类似的东西。不过,在我幼稚的眼中,它看起来像是Constable允许您的用户类“插入”Java编译器并允许它为你做不断的写作。我也知道里面的类和方法java.lang.constant主要是为编译器编写者准备的,但Constable在我看来有点例外。最后,我显然明白我可以调用我希望随时使用这种方法,但这显然不是它的目的。)
编辑:(非常)感谢下面一些非常有帮助和耐心的答案和评论,我想我开始明白了(我不是编译器,这点应该很明显)。虽然我知道一旦一个X implements Constableexists的实例,那么ContantDesc它从它的返回describeConstable()必须由其他常量描述符(本身)组成,虽然我知道ClassDesc#of()可以在编译时调用“常量工厂”(例如等等),显然必须只接受其他常量作为他们可能需要的任何参数,我仍然不清楚 X implements Constable首先在编译期间如何实例化任意对象,而......它正在被编译(!)describeConstable() 可以在编译时调用它。
请记住,这个问题的答案可能是我所缺少的关于编译器的基本信息,或者他们在静态分析期间遇到的各种问题。我只看到一个实例方法 ( describeConstable()),它需要在对象 ( X implements Constable)的实例上调用,并且为了拥有对象的实例,必须有人调用其构造函数。我不清楚 Java 编译器如何知道如何X implements Constable使用其任意的、可能是多参数的构造函数构造 my以便它可以调用describeConstable()它。
我会说到目前为止我所理解和知道的。这确实是一个有趣的功能。
谁调用了 Constable 的 describeConstable()
javac 将要。
什么时候?
当它第一次被调用/需要时。
更详细的解释。你知道 lambdas 是如何编译的吗?如果没有,这里是非常简短的介绍(稍后会很有帮助):
Runnable r = () -> {System.out.println("easy, peasy");};
r.run();
Run Code Online (Sandbox Code Playgroud)
如果您查看字节码,将会有一个invokedynamic调用:
invokedynamic #7, 0 // InvokeDynamic #0:run:()Ljava/lang/Runnable;
Run Code Online (Sandbox Code Playgroud)
反过来,这将调用“引导程序”方法:
BootstrapMethods:
0: #39 REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
Run Code Online (Sandbox Code Playgroud)
引导方法的名称是:LambdaMetafactory::metafactory。
作为输入,它需要一个Lookup(由 JVM 提供)
除此之外,javac提供了一个MethodType(它描述了方法的返回类型和方法参数类型,在这种情况下它是runfrom Runnable)
它将返回 a CallSite(在这种情况下它实际上是 a ConstantCallSite)。
因此,用相当简单的话说(并且很可能有点错误),invokedynamic将调用绑定到 a ConstantCallSite,它在内部将调用委托给使用您提供Runnable的run方法的实现(在内部它委托给“去糖化”私有定义 lambda 的方法)。这只会发生一次,在第一次调用时,所有后续调用都不会经历这种痛苦。不知何故,我在其他答案中提供了更多详细信息,例如此处。
相同的机制将用于动态常量(但它必须使用ldc而不是invokedynamic)。jdk-11 中已经提供了“机器” 。注意类的名称 : ConstantBootstraps,我们知道为什么是“bootstrap”,我们知道为什么是“Constant”。如果你看一下这些参数,它肯定会开始变得有意义,因为它真的很像invokedynamicfor lambdas。
现在您知道为什么Constable/ConstantDesc需要了:以便引导方法调用正确的实现。在上述情况下,javac“知道”(推断/推断/等)lambda 确实是一个Runnable. 在“恒定动态”的情况下,该信息将通过类实现的事实隐含Constable。这将是如何构建常量的“配方”;至少在我的理解中。
请注意,其他人已经在JVMScala 的lazy. 但他们只是简单地在幕后实施双重检查锁定volatile,有时您需要为阅读付费……当然,在幕后实施这一点JVM是有益的;到什么程度,究竟如何尚未可知; 因为这在javac尚未实现,至少在主流 jdk 中没有实现。可能是这样的:
// made-up syntax
__@lazy__
private static final MyObject obj = null;
Run Code Online (Sandbox Code Playgroud)
这最终将委托给Constable::describeConstable或可能是:
__@lazy(provider="myProvider")__
private static final MyObject obj = null;
private MyObject myProvider(){....}
Run Code Online (Sandbox Code Playgroud)
但我敢打赌,比我聪明得多的人会想出我在这里没有提到的如何使用它的想法。当这种情况发生时(我知道它会发生),我需要更新这篇文章。