有趣的是,无论字段是否标记为
static
,代码都将编译 - 在IntelliJ中,它会在使用静态字段时抱怨(但编译),而在使用非静态字段时则不会说一句话。
你是正确的,JLS §8.1.3.2 对[静态]最终字段有特定的规定。然而,在这里扮演重要角色的还有其他一些关于最终字段的规则,来自于Java语言规范
§4.12.4 - 它们指定了
final
字段的编译语义。
但在我们深入探讨这个问题之前,我们需要确定当我们看到
throws
时会发生什么 - 这是由
§14.18给出的,重点在我身上:
一个throw语句会导致异常(§11)被抛出。结果是立即转移控制(§11.3),直到找到捕获抛出值的try语句(§14.20),可能退出多个语句和多个构造函数、实例初始化程序、静态初始化程序和字段初始化程序的评估以及方法调用。如果没有找到这样的try语句,则执行执行throw的线程(§17)在属于该线程的线程组的uncaughtException方法调用之后终止(§11.3)。
通俗地说,在运行时,如果我们遇到throws语句,它可以中断构造函数的执行(正式地说,“突然完成”),导致对象未被构造或以不完整的状态构造。这可能是一个安全漏洞,取决于平台和构造函数的部分完整性。
JVM期望的是:根据§4.5,一个被设置了
ACC_FINAL
的字段在
对象构造之后就不再被赋值。因此,我们在运行时期望这种行为,但在编译时并非如此。如果该字段带有
static
,IntelliJ会引发轻微的麻烦,否则不会。首先,回到
throws
- 只有当
以下三个条件中的一个未满足时,才会出现编译时错误。
- 被抛出的表达式未经检查或为空,
- 您尝试使用正确类型捕获异常,或者
- 被抛出的表达式实际上是可以被抛出的,根据§8.4.6和§8.8.5。
因此,编译带有throws
的构造函数是合法的。恰好在运行时,它总是会突然终止。
如果一个throw语句包含在构造函数声明中,但它的值没有被某个包含它的try语句捕获,那么调用构造函数的类实例创建表达式将因为throw而突然终止(§15.9.4)。
现在,到了那个空白的final
字段。它们有一个奇怪的特点 - 它们的赋值只在构造函数结束后才有意义,强调他们。
空白的final实例变量必须在它所在的类的每个构造函数(§8.8)的末尾明确定义(§16.9);否则会发生编译时错误。
如果我们永远无法到达构造函数的结尾,会发生什么?
第一个程序:正常实例化一个static final
字段,反编译:
public class com/stackoverflow/sandbox/DecompileThis {
private final static I i = 10
public <init>()V
L0
LINENUMBER 7 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
L1
LINENUMBER 9 L1
RETURN
L2
LOCALVARIABLE this Lcom/stackoverflow/sandbox/DecompileThis; L0 L2 0
MAXSTACK = 1
MAXLOCALS = 1
}
请注意,我们在成功调用
<init>
后实际上调用了
RETURN
指令。这是有意义的,也是完全合法的。
第二个程序:在构造函数中抛出异常并使用空的
static final
字段,反编译结果如下:
public class com/stackoverflow/sandbox/DecompileThis {
private final static I i
public <init>()V throws java/lang/InstantiationException
L0
LINENUMBER 7 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
L1
LINENUMBER 8 L1
NEW java/lang/InstantiationException
DUP
LDC "Nothin' doin'."
INVOKESPECIAL java/lang/InstantiationException.<init> (Ljava/lang/String;)V
ATHROW
L2
LOCALVARIABLE this Lcom/stackoverflow/sandbox/DecompileThis; L0 L2 0
MAXSTACK = 3
MAXLOCALS = 1
}
ATHROW
的规则表明引用被弹出,如果有异常处理程序存在,那个会包含处理异常指令的地址。否则,它将从堆栈中移除。
我们从未显式地 return,因此暗示我们从未完成对象的构造。因此,可以认为该对象处于一个奇怪的半初始化状态,同时遵守编译时规则——也就是说,所有语句都是可达的。
对于静态字段而言,由于它不被视为实例变量,而是类变量,这种调用似乎是错误的。可能值得提交一个错误报告。
回想一下,从上下文来看这是有道理的,因为在Java中以下声明是合法的,而方法体与构造函数体是相似的:
public boolean trueOrDie(int val) {
if(val > 0) {
return true;
} else {
throw new IllegalStateException("Non-natural number!?");
}
}
JDK javac
进行编译。 - Vishal K