有趣的是,无论字段是否被标记,代码都会编译static
- 在 IntelliJ 中,它会抱怨(但编译)静态字段,而不是对非静态字段说一句话。
你是对的,JLS §8.1.3.2 有一些关于 [静态] 最终字段的规则。但是,这里还有一些其他关于 final 字段的规则,它们来自 Java 语言规范§4.12.4 - 它指定了final
字段的编译语义。
但在我们进入那个蜡球之前,我们需要确定当我们看到时会发生什么——这是§14.18throws
给我们的,强调我的:
throw 语句会引发异常(§11)。结果是立即转移控制(第 11.3 节),可能会退出多个语句和多个构造函数、实例初始化程序、静态初始化程序和字段初始化程序评估以及方法调用,直到找到捕获抛出值的 try 语句(第 14.20 节)。如果没有找到这样的 try 语句,则在为线程所属的线程组调用 uncaughtException 方法后终止执行 throw 的线程(第 17 节)的执行(第 11.3 节)。
通俗地说——在运行时,如果我们遇到一个throws
语句,它可以中断构造函数的执行(正式地,“突然完成”),导致对象不能被构造,或者构造成不完整的状态。这可能是一个安全漏洞,具体取决于平台和构造函数的部分完整性。
§4.5 给出的 JVM 期望的是,具有ACC_FINAL
set的字段在构造对象后永远不会设置其值:
宣布终局;从未直接分配给对象构造后(JLS §17.5)。
所以,我们有点麻烦 - 我们希望在运行时出现这种行为,但在编译时不会。为什么 IntelliJ 会在我static
在那个领域有小题大做,而不是在我没有的时候呢?
首先,回到- 如果不满足这三个部分之一,throws
则该语句只有一个编译时错误:
- 抛出的表达式未经检查或为空,
- 你
try
是catch
例外,你正在catch
使用正确的类型,或者
- 根据 §8.4.6 和 §8.8.5,被抛出的表达式实际上是可以抛出的。
所以用 a 编译构造函数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
}
观察我们RETURN
在成功调用我们的<init>
. 有道理,而且完全合法。
第二个程序:抛出构造函数和空白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
}
的规则ATHROW
表明引用被弹出,并且如果那里有异常处理程序,它将包含处理异常的指令的地址。否则,它会从堆栈中移除。
我们从不明确地return,因此暗示我们永远不会完成对象的构造。因此,可以认为该对象处于不稳定的半初始化状态,同时遵守编译时规则——也就是说,所有语句都是可访问的。
在静态字段的情况下,由于它不被视为实例变量,而是类变量,因此允许这种调用似乎是错误的。可能值得提交一个错误。
回想一下,它在上下文中确实有一定的意义,因为 Java 中的以下声明是合法的,并且方法体与构造函数体是一致的:
public boolean trueOrDie(int val) {
if(val > 0) {
return true;
} else {
throw new IllegalStateException("Non-natural number!?");
}
}