4

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.

Community
  • 1
  • 1
noamtm
  • 12,435
  • 15
  • 71
  • 107
  • 1
    Creating the instance and initializing it are separate things: the instance is created first, and then it is initialized via its constructor; so even if the object isn't initialized fully, an instance still exists. This is an example of *unsafe publication*, and you should (obviously) avoid it. – Andy Turner Jun 16 '16 at 11:51
  • 1
    Possible duplicate of [In Java what happens when an object fails to be instantiated?](http://stackoverflow.com/questions/3421606/in-java-what-happens-when-an-object-fails-to-be-instantiated) – Julien Lopez Jun 16 '16 at 12:03
  • @JulienLopez not a dupe, please see the edit. – noamtm Jun 16 '16 at 12:12

1 Answers1

8

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.

Andy Turner
  • 137,514
  • 11
  • 162
  • 243
  • I can't think of any other way (except from within the constructor) of getting that object's reference. Which means this can only happen if the class's author is not careful, by saving the reference before the last line of the constructor (ignore the fact that saving an object's reference in a static field that way is weird). – noamtm Jun 16 '16 at 12:06
  • Yep, it's all about what you do in the constructor; it has to be, because once the constructor is finished, the instance is "fully initialized". Starting a thread in the constructor, where the thread references the containing instance, is a common example. Also, if you pass `this` to an alien method, you can't generally know where that reference will end up, so that is unsafe publication too. – Andy Turner Jun 16 '16 at 12:08