有趣的是,代码将编译无论字段是否被标记为静态 – 并且在IntelliJ中,它将使用静态字段来投诉(但编译),而不是使用非静态字段来说一个字。
你是对的,JLS§8.1.3.2有关于[静态]最终字段的某些规则。然而,关于final字段的一些其他规则在这里起着很大的作用,来自Java语言规范§4.12.4 – 它指定了最终字段的编译语义。
但是在我们进入那个蜡球之前,我们需要确定当我们看到投掷时会发生什么 – 这是§14.18给我们的,强调我的:
A throw statement causes an exception (§11) to be thrown. The result is an immediate transfer of control (§11.3) that may exit multiple statements and multiple constructor, instance initializer, static initializer and field initializer evaluations, and method invocations until a try statement (§14.20) is found that catches the thrown value. If no such try statement is found, then execution of the thread (§17) that executed the throw is terminated (§11.3) after invocation of the uncaughtException method for the thread group to which the thread belongs.
在外行人的术语中 – 在运行时,如果我们遇到throws语句,它可以中断构造函数的执行(正式地,“突然完成”),导致对象不被构造或构造在不完整的状态。这可能是一个安全漏洞,这取决于平台和构造函数的部分完整性。
Declared final; never directly assigned to after object construction (JLS §17.5).
所以,我们有点腌渍 – 我们期望这在运行时,但不是在编译期间的行为。为什么IntelliJ在这个领域有静态时会引起轻微的轰动,但是当我没有静态时呢?
>正在抛出的表达式未选中或为null,
>你试图捕获异常,你正在捕捉它的类型,或者
>根据§8.4.6和§8.8.5,被抛出的表达式实际上可以被抛出。
因此,使用throws编译构造函数是合法的。它只是这样发生,在运行时,它将永远完成突然。
If a throw statement is contained in a constructor declaration, but its value is not caught by some try statement that contains it, then the class instance creation expression that invoked the constructor will complete abruptly because of the throw (§15.9.4).
现在,到那个空白的最后一个字段。有一个好奇的作品给他们 – 他们的任务只有在构造函数结束后才重要,强调他们。
A blank final instance variable must be definitely assigned (§16.9) at the end of every constructor (§8.8) of the class in which it is declared; otherwise a compile-time error occurs.
如果我们从未到达构造函数的结尾怎么办?
第一个程序:静态最终字段的正常实例化,反编译:
// 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 ()V
L0
LINENUMBER 7 L0
ALOAD 0
INVOKESPECIAL java/lang/Object. ()V
L1
LINENUMBER 9 L1
RETURN //
L2
LOCALVARIABLE this Lcom/stackoverflow/sandbox/DecompileThis; L0 L2 0
MAXSTACK = 1
MAXLOCALS = 1
}
观察到我们实际上在成功调用我们的< init>之后调用RETURN指令。有意义,是完全合法的。
第二个程序:抛出构造函数和空白静态最终字段,反编译:
// 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 ()V throws java/lang/InstantiationException
L0
LINENUMBER 7 L0
ALOAD 0
INVOKESPECIAL java/lang/Object. ()V
L1
LINENUMBER 8 L1
NEW java/lang/InstantiationException
DUP
LDC "Nothin' doin'."
INVOKESPECIAL java/lang/InstantiationException. (Ljava/lang/String;)V
ATHROW //
L2
LOCALVARIABLE this Lcom/stackoverflow/sandbox/DecompileThis; L0 L2 0
MAXSTACK = 3
MAXLOCALS = 1
}
The rules of ATHROW表示引用被弹出,并且如果在那里有一个异常处理程序,它将包含处理异常的指令的地址。否则,它将从堆栈中删除。
我们从不显式返回,因此意味着我们从不完成对象的构造。因此,对象可以被认为处于一个初始化的半初始化状态,即所有语句都是可达的。
在静态字段的情况下,由于它不被认为是一个实例变量,而是一个类变量,这种调用看起来是错误的。这可能值得提出一个bug。
回想一下,它在上下文中确实有意义,因为Java中的以下声明是合法的,并且方法体与构造函数体是一致的:
public boolean trueOrDie(int val) {
if(val > 0) {
return true;
} else {
throw new IllegalStateException("Non-natural number!?");
}
}