Java:构造函数失败的对象会发生什么?

时间:2016-06-16 11:49:19

标签: java exception constructor null

请考虑以下代码段:

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的对象,即使它已声明为@NonNullfinal

test()函数无法直接获取对生成的t1的引用。在catch块之后,t1为空。 然而,Test1.instance很好,调用它的dump()函数显示date1已初始化但date2null

这里发生了什么?为什么我可以保持对真正处于非法状态的对象的引用?

修改 t1为空的事实是显而易见的(与In Java what happens when an object fails to be instantiated?不同)。这个问题是关于设法存储在静态字段中的对象的状态。

1 个答案:

答案 0 :(得分:8)

考虑以下类的字节码:

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行)。

因此,即使它没有完全初始化,实例也存在;并且您可以将该实例的引用分配给另一个引用。

在完全初始化之前将实例分配给静态字段是 unsafe publication 的一个示例,您应该(显然)避免它。