Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java: what happens to an object whose constructor has failed?

Consider this snippet:

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's constructor always throws an exception right after assigning itself to a static field. That field keeps a reference to a partially initialized object: an object who's date2 field is null, even though it's declared @NonNull and final.

The test() function can't directly get a reference to the generated t1. After the catch block, t1 is null. And yet, Test1.instance is just fine, and calling its dump() function shows that date1 is initialized but date2 is null.

What's going on here? Why can I keep a reference to an object that is really in an illegal state?

EDIT That fact that t1 is null is obvious (unlike In Java what happens when an object fails to be instantiated?). This question is about the state of the object that managed to get stored in the static field.

like image 368
noamtm Avatar asked Feb 07 '23 18:02

noamtm


1 Answers

Consider the bytecode for the following class:

class Foo {
  public static void main(String[] args) {
    new Foo();
  }
}

Bytecode:

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
}

You can see from this that creation of the new instance and the invocation of the constructor are separate (lines 0 and 4 in main).

So, even if it is not fully initialized, the instance exists; and you can assign a reference to that instance to another reference.

Assigning the instance to a static field before it is fully initialized is an example of unsafe publication, and you should (obviously) avoid it.

like image 121
Andy Turner Avatar answered Feb 09 '23 14:02

Andy Turner