据我所知,Java 通过在编译时进行常量折叠来处理常量变量§4.12.4。我已经尽力了,但我无法从 JLS 中找到它的描述。谁能告诉我在哪里可以找到Java 11的常量折叠过程的官方描述?
3 回答
该规范不使用术语恒定折叠。
它有常量表达式的定义
常量表达式是表示原始类型的值或
String
不突然完成并且仅使用以下内容组成的表达式:[…]
类型的常量表达式
String
始终是“内部的”,以便使用方法共享唯一实例String.intern
。常量表达式始终被视为 FP-strict ( §15.4 ),即使它出现在非常量表达式不会被视为 FP-strict 的上下文中。
常量表达式用作语句中的
case
标签(第 14.11 节),在赋值上下文(第 5.2 节)和类或接口的初始化(第 12.4.2 节)中具有特殊意义。它们还可以控制 、 或 语句正常完成的能力(第14.21节),以及带有数字操作数的条件运算符的类型。switch
while
do
for
? :
最后一部分已经指出了常量表达式的预先计算是强制性的。当涉及到case
标签时,编译器需要报告重复项,因此,它必须在编译时计算值。在计算循环时,它必须计算常量布尔表达式来确定代码的可达性。
同样,初始化器需要预先计算来确定正确性。egshort s = 'a' * 2;
是一个正确的声明,但short s = Short.MAX_VALUE + 1;
不是。
常量表达式的一个众所周知的用例是常量变量的初始化器。读取常量变量时,会使用常量值代替读取变量,对比问答“Does the JLS require inlining of final String constants?”</a>
但这并不意味着“不断折叠”是强制性的。理论上,一致的实现仍然可以在使用变量的每个地方执行变量初始化程序中写入的常量表达式的计算。在实践中,字节码格式会导致恒定的折叠行为。用于记录字节码中常量变量值的ConstantValue
属性只能保存一个预先计算好的值。在针对已编译的类文件进行编译时,编译器无法使用常量变量的原始表达式。它只能使用预先计算的值。
同样,编译switch
指令通常使用 thetableswitch
或lookupswitch
指令来完成,两者都需要预先计算int
的case
标签值。编译器必须花很长时间才能实现不同的策略。
此外,注释值的编译格式只能保存预先计算的表达式。
Java 语言规范定义了语言的语义;常量折叠是一种编译器优化,它不会改变 Java 程序的行为,因此在 JLS 中没有指定,也不需要。允许 Java 的实现不这样做,或者在某些情况下不这样做,但在其他情况下不这样做,只要编译的程序按照 JLS 所说的去做。
也就是说,JLS 确实以这样一种方式定义了语言语义,即允许在更多情况下进行常量折叠而不改变程序的行为。最相关的段落可能是您在§14.2.4中所指的内容:
常量变量是使用常量表达式(第 15.28 节)初始化的原始类型或字符串类型的最终变量。变量是否为常量变量可能对类初始化(第 12.4.1 节)、二进制兼容性(第 13.1 节)、可达性(第 14.21 节)和明确赋值(第 16.1.1 节)有影响。
关于类初始化、二进制兼容性、可达性和明确赋值的引用部分专门定义了常量变量的语义,与其他变量不同;具体来说,它们定义的行为是您对折叠常量的编译器所期望的行为。这允许那些实施规范的人在不过度限制他们如何做的情况下进行优化。
字段过程从问题中链接的页面链接:https ://docs.oracle.com/javase/specs/jls/se11/html/jls-13.html#jls-13.1
对作为常量变量的字段(第 4.12.4 节)的引用必须在编译时解析为由常量变量的初始化程序表示的值 V。
如果这样的字段是静态的,则二进制文件的代码中不应存在对该字段的引用,包括声明该字段的类或接口。这样的字段必须始终显示为已初始化(第 12.4.2 节);决不能观察该字段的默认初始值(如果不同于 V)。
如果这样的字段是非静态的,则在二进制文件的代码中不应出现对该字段的引用,除非在包含该字段的类中。(它将是一个类而不是一个接口,因为一个接口只有静态字段。)该类应该具有在实例创建期间将字段的值设置为 V 的代码(第 12.5 节)。