Java explicit constructor invocation & Instance Initializer

Wuaner picture Wuaner · Sep 3, 2012 · Viewed 9.4k times · Source

In The Java Tutorials - Initializing Fields, have a description about Instance Initialization Blocks(Instance Initializer):

The Java compiler copies initializer blocks into every constructor. Therefore, this approach can be used to share a block of code between multiple constructors.

If the paraphrase is correct , for the following codes:

public class ConstructorTest {

    public static void main(String[] args) {
        Child c = new Child();
    }
}

class Parent {
    Parent() {
        System.out.println("Parent non-argument Constructor");
    }
}

class Child extends Parent {

    {
        System.out.println("Child Instance Initialization Block");
    }

    Child() {
        this(2);
        System.out.println("Child no-argument Constructor");

    }

    Child(int i) {
        this(10, i);
        System.out.println("Child 1-argument Constructor");
    }

    Child(int i, int j) {
        System.out.println("Child 2-argument Constructor");
    }
}

The output should be:

Parent non-parm Constructor
Child Instance Initialization Block
Child 2-argument Constructor
Child Instance Initialization Block
Child 1-argument Constructor
Child Instance Initialization Block
Child no-argument Constructor

But the actually output is:

Parent non-argument Constructor
Child Instance Initialization Block
Child 2-argument Constructor
Child 1-argument Constructor
Child no-argument Constructor

I was misunderstood the meaning of that sentence, or the description is not accurate enough?

And another doubt about explicit constructor invocation:

Based on two Basics:

  • If present, the invocation of another constructor must be the first line in the constructor.
  • From within a constructor, use this() to call another constructor,use super() to call direct superclass's Corresponding constructor.

Is that MEANS use this() within subclass's constructor will implicit remove the default call to the no-argument constructor of the superclass?

Thanks for the reply.

Answer

Jon Skeet picture Jon Skeet · Sep 3, 2012

EDIT: It turns out the JLS is accurate after all, although it's hard to read. It's all detailed in section 12.5:

Just before a reference to the newly created object is returned as the result, the indicated constructor is processed to initialize the new object using the following procedure:

  1. Assign the arguments for the constructor to newly created parameter variables for this constructor invocation.

  2. If this constructor begins with an explicit constructor invocation (§8.8.7.1) of another constructor in the same class (using this), then evaluate the arguments and process that constructor invocation recursively using these same five steps. If that constructor invocation completes abruptly, then this procedure completes abruptly for the same reason; otherwise, continue with step 5.

  3. This constructor does not begin with an explicit constructor invocation of another constructor in the same class (using this). If this constructor is for a class other than Object, then this constructor will begin with an explicit or implicit invocation of a superclass constructor (using super). Evaluate the arguments and process that superclass constructor invocation recursively using these same five steps. If that constructor invocation completes abruptly, then this procedure completes abruptly for the same reason. Otherwise, continue with step 4.

  4. Execute the instance initializers and instance variable initializers for this class, assigning the values of instance variable initializers to the corresponding instance variables, in the left-to-right order in which they appear textually in the source code for the class. If execution of any of these initializers results in an exception, then no further initializers are processed and this procedure completes abruptly with that same exception. Otherwise, continue with step 5.

  5. Execute the rest of the body of this constructor. If that execution completes abruptly, then this procedure completes abruptly for the same reason. Otherwise, this procedure completes normally.

Note the highlighted part - the chained constructor is executed, and then we skip step 4, which would execute the instance initializer.

Reality is that the instance and field initializers are only executed once, as you can tell from your output.

Informally, I believe it's accurate to describe the procedure as:

  • Keep chaining constructors within the same class (this(...)) until you reach a constructor body which doesn't start with this.
  • Execute the appropriate super-constructor
  • Execute the instance variable initializers and the instance initializers
  • Execute the body of the "innermost" constructor
  • Keep popping the stack of constructor bodies until you end up with the "entry" constructor

Is that MEANS use this() within subclass's constructor will implicit remove the default call to the no-argument constructor of the superclass?

Yes. Somewhere in your chain of constructors within the class, you're guaranteed to end up with a constructor which calls super either implicitly or explicitly. That is the only superclass constructor which is called.

EDIT: Note that the tutorial you quoted is demonstrably incorrect.

Sample class:

public class Test {
    {
        System.out.println("Foo");
    }
    
    public Test() {
    }
    
    public Test(int i)  {
        this();
    }
}

Output from javap -c:

public class Test {
  public Test();
    Code:
       0: aload_0
       1: invokespecial #1  // Method java/lang/Object."<init>": ()V
       4: getstatic     #2  // Field java/lang/System.out:Ljava/io/PrintStream;
       7: ldc           #3  // String Foo
       9: invokevirtual #4  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      12: return

  public Test(int);
    Code:
       0: aload_0
       1: invokespecial #5                  // Method "<init>":()V
       4: return
}

As you can see, the constructor for Test(int) does not have the code for the instance constructor compiled into it.

Basically, only constructors which directly call superclass constructors have instance initializer code copied into them. All other constructors will end up causing the instance initializer code to be executed via a constructor which calls the superclass constructor, of course.