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