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

Java:构造函数失败的对象会发生什么情况?,java,exception,constructor,null,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 (tru

考虑一下这个片段:

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
final

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

这是怎么回事?为什么我可以保留对确实处于非法状态的对象的引用

编辑
t1
为空这一事实是显而易见的(不像)。这个问题是关于设法存储在静态字段中的对象的状态。

考虑以下类的字节码:

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
}
class-Foo{
Foo();
代码:
0:aload_0
1:invokespecial#1//方法java/lang/Object。“:()V
4:返回
公共静态void main(java.lang.String[]);
代码:
0:new#2//class Foo
3:dup
4:invokespecial#3//方法“”:()V
7:流行音乐
8:返回
}
从中可以看出,新实例的创建和构造函数的调用是分开的(在
main
中的第0行和第4行)

因此,即使没有完全初始化,实例仍然存在;您可以将对该实例的引用分配给另一个引用


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

创建实例和初始化实例是两件独立的事情:首先创建实例,然后通过其构造函数初始化它;因此,即使对象没有完全初始化,实例仍然存在。这是一个不安全发布的示例,您应该(显然)避免它。@JulienLopez的可能重复不是重复,请查看编辑。我想不出任何其他方法(除了从构造函数中)来获取该对象的引用。这意味着只有在类的作者不小心的情况下才会发生这种情况,在构造函数的最后一行之前保存引用(忽略这样在静态字段中保存对象引用很奇怪的事实);必须这样做,因为一旦构造函数完成,实例就“完全初始化”。在构造函数中启动线程(线程引用包含实例)是一个常见的示例。另外,如果您将
这个
传递给一个外来方法,您通常不知道该引用将在哪里结束,因此这也是不安全的发布。