noa*_*mtm 4 java null constructor exception
请考虑以下代码段:
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;
}
}
Run Code Online (Sandbox Code Playgroud)
Test1的构造函数在将自身分配给静态字段后总是抛出异常.那场保持对部分初始化对象的引用的对象是谁的date2领域是null,即使它宣布@NonNull和final.
该test()函数不能直接获取对生成的t1的引用.在catch块之后,t1为空.然而,Test1.instance只是罚款,并调用其dump()功能显示date1初始化,但是date2是null.
这里发生了什么?为什么我可以保持对真正处于非法状态的对象的引用?
编辑
那个t1为null的事实很明显(与Java不同,当一个对象无法实例化时会发生什么?).这个问题是关于设法存储在静态字段中的对象的状态.
考虑以下类的字节码:
class Foo {
public static void main(String[] args) {
new Foo();
}
}
Run Code Online (Sandbox Code Playgroud)
字节码:
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
}
Run Code Online (Sandbox Code Playgroud)
您可以从中看到,新实例的创建和构造函数的调用是分开的(第0行和第4行main).
因此,即使它没有完全初始化,实例也存在; 并且您可以将该实例的引用分配给另一个引用.
在完全初始化之前将实例分配给静态字段是不安全发布的一个示例,您应该(显然)避免它.