以下代码:
public class StaticFinal
{
private final static int i ;
public StaticFinal()
{}
}
我得到编译时错误:
StaticFinal.java:7: variable i might not have been initialized
{}
^
1 error
这符合JLS8.3.1.2,其中说:
如果空白的final(§4.12.4)类变量未被声明它的类的静态初始化程序(第8.7节)明确赋值(第16.8节),则是编译时错误。
因此,完全理解上述错误 但现在考虑以下几点:
public class StaticFinal
{
private final static int i ;
public StaticFinal()throws InstantiationException
{
throw new InstantiationException("Can't instantiate"); // Don't let the constructor to complete.
}
}
这里,构造函数永远不会完成,因为InstantiationException
被抛出在构造函数的中间。 这段代码编译得很好!!
为什么?为什么此代码未显示有关final
变量i
的非初始化的编译时错误?
的修改
我在命令提示符下使用javac 1.6.0_25
编译它(不使用任何IDE )
答案 0 :(得分:3)
有趣的是,代码将编译该字段是否标记为static
- 并且在IntelliJ中,它将使用静态字段抱怨(但编译),而不是用非静态字段说出一个字
你是对的,因为JLS§8.1.3.2对[静态]最终字段有一定的规则。但是,还有一些围绕最终字段的其他规则在Java语言规范§4.12.4中发挥了重要作用,它们指定了final
字段的编译语义。
但是在我们进入那个蜡球之前,我们需要确定当我们看到throws
时会发生什么 - 这是§14.18给我们的,强调我的:
throw语句导致抛出异常(§11)。结果是立即转移控制(第11.3节),可以退出多个语句和多个构造函数,实例初始化器,静态初始化器和字段初始化器评估,以及方法调用,直到try语句(§14.20)为止发现抓住了抛出的价值。如果没有找到这样的try语句,则在调用该线程所属的线程组的uncaughtException方法之后,终止执行执行throw的线程(第17节)(第11节)。
用外行的术语 - 在运行期间,如果我们遇到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
使用正确的类型,或因此使用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
}
The rules of ATHROW
表示弹出了引用,如果有异常处理程序, 将包含处理异常的指令的地址。否则,它将从堆栈中删除。
我们从未明确返回,因此暗示我们永远不会完成对象的构建。因此,可以认为该对象处于一个不稳定的半初始化状态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!?");
}
}
答案 1 :(得分:1)
正如我在这里所理解的那样,我们都是开发人员,所以我相信我们不会在我们中找到真正的反应......这件事与编译器内部有关......我认为是一个bug,或者至少是一种不必要的行为。
排除Eclipse,它有一些增量编译器(因此能够立即检测到问题),命令行javac执行一次性编译。现在,第一个片段
public class StaticFinal {
private final static int i ;
}
与使用空构造函数(如第一个示例中)基本相同,是抛出编译时错误,这很好,因为它符合规范。
在第二个片段中,我认为编译器中存在一个错误;似乎编译器根据构造函数的作用做出一些决定。如果你试图编译这个,那就更明显了,
public class StaticFinal
{
private final static int i ;
public StaticFinal()
{
throw new RuntimeException("Can't instantiate");
}
}
这比你的例子更奇怪,因为未经检查的异常未在方法签名中声明,并且仅在运行时发现(至少这是我在阅读本文之前所想到的)。
观察我可以说的行为(但根据规格是错误的)。
对于静态最终变量,编译器会尝试查看它们是在显式初始化,还是在静态的intializer块中初始化,但是,由于某些奇怪的原因,它也会在构造函数中查找某些内容:
答案 2 :(得分:0)
添加main方法后,打印代码i
。代码打印值0.这意味着java编译器自动初始化值为0的i。
我在IntelliJ中编写它并且必须禁用代码检查才能构建代码。否则它不会让我在抛出异常之前给你同样的错误。
public class StaticFinal {
private final static int i;
public StaticFinal(){
throw new InstantiationError("Can't instantiate!");
}
public static void main(String args[]) {
System.out.print(i);
}
}
同
public class StaticFinal {
private final static int i = 0;
public StaticFinal(){
throw new InstantiationError("Can't instantiate!");
}
public static void main(String args[]) {
System.out.print(StaticFinal.i);
}
}
public class StaticFinal
{
public StaticFinal()
{
throw new InstantiationError("Can't instantiate!");
}
public static void main(String args[])
{
System.out.print(0);
}
private static final int i = 0;
}
在反编译代码之后,事实证明并非如此。由于反编译代码和原始代码相同。唯一的另一种可能性是初始化是通过Java虚拟机完成的。我所做的最后一次改变是一个足够好的证据表明情况就是如此。
要发现这一点,必须说得好。
相关问题: Here
答案 3 :(得分:-2)
我会说这只是因为当你添加Throws
时,你基本上是在处理错误,所以编译器会“哦,好吧,他可能知道他在做什么”。毕竟它仍会产生运行时错误。