考虑这个片段:
class Test1 {
private static Test1 instance;
@NonNull private final Date date1;
@NonNull private final Date date2;
Test1() throws Exception {
this.date1 = new Date();
Test1.instance = this;
if (true) {
throw new Exception();
}
this.date2 = new Date();
}
public void dump() {
System.out.println("date1: " + date1);
System.out.println("date2: " + date2);
}
static void test() {
Test1 t1 = null;
try {
t1 = new Test1();
} catch (Exception e) {
e.printStackTrace();
}
Test1.instance.dump();
assert t1 == null;
}
}
Test1 的构造函数总是在将自身分配给静态字段后立即抛出异常。该字段保留对部分初始化对象的引用:date2
字段为 null
的对象,即使它被声明为 @NonNull
和 最终
。
test()
函数无法直接获取生成的 t1 的引用。在 catch
block 之后,t1 为空。
然而,Test1.instance 很好,调用它的 dump()
函数显示 date1
已初始化,但 date2
为 null
。
这是怎么回事?为什么我可以保留对真正处于非法状态的对象的引用?
编辑
t1
为空这一事实是显而易见的(与 In Java what happens when an object fails to be instantiated? 不同)。这个问题是关于设法存储在静态字段中的对象的状态。
最佳答案
考虑以下类的字节码:
class Foo {
public static void main(String[] args) {
new Foo();
}
}
字节码:
class Foo {
Foo();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class Foo
3: dup
4: invokespecial #3 // Method "<init>":()V
7: pop
8: return
}
从这里可以看出,新实例的创建和构造函数的调用是分开的(main
中的第 0 行和第 4 行)。
所以,即使没有完全初始化,实例也是存在的;您可以将对该实例的引用分配给另一个引用。
在实例完全初始化之前将实例分配给静态字段是不安全发布的一个示例,您应该(显然)避免这种情况。
关于Java:构造函数失败的对象会发生什么?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/37858328/