有趣的是,无论字段是否标记为static,代码都会编译 - 而在 IntelliJ 中,它会抱怨(但编译)静态字段,而不是非静态字段。
您是对的,JLS §8.1.3.2 对 [静态] 最终字段有某些规则。但是,还有一些其他规则围绕 final 字段发挥重要作用,来自 Java 语言规范 §4.12.4 - 它指定了 final 字段的编译语义。
但在我们进入那个蜡球之前,我们需要确定当我们看到throws 时会发生什么 - 这是§14.18 给我们的,强调我的:
throw 语句会引发异常(第 11 节)。结果是立即转移控制(第 11.3 节)可能会退出多个语句和多个构造函数、实例初始化程序、静态初始化程序和字段初始化程序评估以及方法调用,直到出现 try 语句(第 14.20 节)发现捕获了抛出的值。如果没有找到这样的 try 语句,则在为线程所属的线程组调用 uncaughtException 方法后终止执行 throw 的线程(第 17 节)的执行(第 11.3 节)。
通俗的说——在运行时,如果我们遇到throws语句,它会中断构造函数的执行(正式地,“突然完成”),导致对象不能被构造,或者在一个不完整的状态。这可能是一个安全漏洞,具体取决于平台和构造函数的部分完整性。
第 4.5 节给出的 JVM 所期望的是a field with ACC_FINAL set never has its value set after construction of the object:
宣布终局;从未在对象构造之后直接分配(JLS §17.5)。
所以,我们有点麻烦 - 我们希望在 运行时 期间出现这种行为,而不是在 编译时 期间。为什么当我在该领域有 static 时 IntelliJ 会引起轻微的大惊小怪,而当我没有时却不会?
首先,回到throws - 如果one of these three pieces aren't satisfied:则该语句只有编译时错误:
- 抛出的表达式未选中或为空,
- 你
try 到 catch 异常,你 catch 使用正确的类型,或者
- 根据 §8.4.6 和 §8.8.5,被抛出的表达式实际上是可以抛出的。
因此使用throws 编译构造函数是合法的。碰巧的是,在运行时,它总是会突然完成。
如果一个 throw 语句包含在构造函数声明中,但它的值没有被包含它的一些 try 语句捕获,那么调用构造函数的类实例创建表达式将由于 throw 突然完成(第 15.9.4 节)。
现在,进入那个空白的final 字段。他们有一个奇怪的部分 - 他们的分配只有在构造函数结束之后才重要,强调他们的。
必须在声明它的类的每个构造函数(第 8.8 节)的末尾明确分配一个空白的最终实例变量(第 16.9 节);否则会发生编译时错误。
如果我们从不到达构造函数的末尾怎么办?
第一个程序:static final 字段的正常实例化,反编译:
// class version 51.0 (51)
// access flags 0x21
public class com/stackoverflow/sandbox/DecompileThis {
// compiled from: DecompileThis.java
// access flags 0x1A
private final static I i = 10
// access flags 0x1
public <init>()V
L0
LINENUMBER 7 L0
ALOAD 0
INVOKESPECIAL java/lang/Object.<init> ()V
L1
LINENUMBER 9 L1
RETURN // <- Pay close attention here.
L2
LOCALVARIABLE this Lcom/stackoverflow/sandbox/DecompileThis; L0 L2 0
MAXSTACK = 1
MAXLOCALS = 1
}
观察我们在成功调用<init> 之后实际上调用了RETURN 指令。有道理,而且完全合法。
第二个程序:抛出构造函数和空白static final字段,反编译:
// class version 51.0 (51)
// access flags 0x21
public class com/stackoverflow/sandbox/DecompileThis {
// compiled from: DecompileThis.java
// access flags 0x1A
private final static I i
// access flags 0x1
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 // <-- Eeek, where'd my RETURN instruction go?!
L2
LOCALVARIABLE this Lcom/stackoverflow/sandbox/DecompileThis; L0 L2 0
MAXSTACK = 3
MAXLOCALS = 1
}
The rules of ATHROW 表示引用已弹出,如果有异常处理程序,that 将包含处理异常的指令地址。否则,它会从堆栈中移除。
我们从不明确地return,因此意味着我们永远不会完成对象的构造。因此,可以认为该对象处于不稳定的半初始化状态,all the while obeying compile-time rules - 也就是说,所有语句都是可访问的。
在静态字段的情况下,由于它不被视为实例变量,而是类变量,因此允许这种调用似乎是错误的。可能值得提交一个错误。
回想起来,它确实在上下文中有些意义,因为以下 Java 声明是合法的,并且方法体与构造函数体是一致的:
public boolean trueOrDie(int val) {
if(val > 0) {
return true;
} else {
throw new IllegalStateException("Non-natural number!?");
}
}