Object Initialization in Java

Preface

Before we get started, let's see a snippet first.

public class Foo {
    {
        bar = 1;
    }

    private int bar = 3;

    private static int baz = 4;

    static {
        baz = 2;
    }

    public Foo() {
        bar = 5;
    }

    public static void main(String[] argv) {
        Foo foo = new Foo();
        System.out.println(foo.bar);
        System.out.println(Foo.baz);
    }
}

What's the output? If you're familiar with Java's initialization mechanism, you can answer the question quickly. I'm gonna talk about the mechanism in this post.

Bytecode View

To get clear about the initialization process, let's disassemble the class file and analyze the instructions. The outputs of javap are listed below.

Compiled from "Foo.java"
public class Foo {
  public Foo();
    Code:
       0: aload_0       
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0       
       5: iconst_1      
       6: putfield      #2                  // Field bar:I
       9: aload_0       
      10: iconst_3      
      11: putfield      #2                  // Field bar:I
      14: aload_0       
      15: iconst_5      
      16: putfield      #2                  // Field bar:I
      19: return        

  public static void main(java.lang.String[]);
    Code:
       0: new           #3                  // class Foo
       3: dup           
       4: invokespecial #4                  // Method "<init>":()V
       7: astore_1      
       8: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      11: aload_1       
      12: getfield      #2                  // Field bar:I
      15: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
      18: getstatic     #5                  // Field java/lang/System.out:Ljava/io/PrintStream;
      21: getstatic     #7                  // Field baz:I
      24: invokevirtual #6                  // Method java/io/PrintStream.println:(I)V
      27: return        

  static {};
    Code:
       0: iconst_4      
       1: putstatic     #7                  // Field baz:I
       4: iconst_2      
       5: putstatic     #7                  // Field baz:I
       8: return        
}

The source code of class Foo is pretty simple, thus the instructions are not hard to read as well.
The code in static block and static variable initializer are merged into a special method by compiler. The static{} method above is also known as <clinit>, it's a method generated by compiler. <clinit> is not a valid method name in Java, so there's no possibility of name collision.
Line 4 in method main is an invokespecial instruction. The invokespecial instruction is only used for instance initialization according to Java Virtual Machine Specification Chapter 2.9, which means compiler generate an <init> method of return type void for class Foo, and that's the real beginning of the object's life.

<init> Method

What's the difference between the generated <init> method and the orginal constructor? The <init> method of class Foo did the following things:

  • Call <init> method of class Object, as Foo inherits from Object
  • Assign 1 to field bar
  • Assign 3 to field bar
  • Assign 5 to field bar

In general, the structure of <init> method is like this:

  1. Invocation of <init> method of super class
  2. Instance variable initializers and initializer blocks, they're executed by line order
  3. Code in original constructor body

Structure like this make sure that fields in super class get proper value before descendant class access them in most case. While if you call overridden method from constructor of super class, it may still cause problems.
There's no constructor defined in java code, but from the bytecode, we know that the compiler generated a default one for class Foo, and it calls the default no-arg constructor of super class Object. If super class has no no-arg constructor, the descendant class have to call super class constructor explicitly, otherwise it won't compile.

this()

What happened if a constructor call another constructor? Here's a rather simple class, and let's disassemble it likewise.

public class Foo {
    private int bar;
    private int baz;

    public Foo() {
        this(1);
    }

    public Foo(int qux) {
        this(qux, 2);
    }

    public Foo(int qux, int quxx) {
        bar = qux;
        baz = quxx;
    }
}
Compiled from "Foo.java"
public class Foo {
  public Foo();
    Code:
       0: aload_0       
       1: iconst_1      
       2: invokespecial #1                  // Method "<init>":(I)V
       5: return        

  public Foo(int);
    Code:
       0: aload_0       
       1: iload_1       
       2: iconst_2      
       3: invokespecial #2                  // Method "<init>":(II)V
       6: return        

  public Foo(int, int);
    Code:
       0: aload_0       
       1: invokespecial #3                  // Method java/lang/Object."<init>":()V
       4: aload_0       
       5: iload_1       
       6: putfield      #4                  // Field bar:I
       9: aload_0       
      10: iload_2       
      11: putfield      #5                  // Field baz:I
      14: return        
}

We can see that this() call is compiled to an invocation of another <init> method of the class. In the end, there's always an <init> method structured like I said in the previous chapter.
The this() or super() invocation have to be the first line of constructor, so the instance variables can be initialized from base class on down.

Conclusion

So the answer is pretty clear. The snippet in preface will have the following output:

5
2

An object is always initialized in following steps:

  1. Static initializer of super class
  2. Instance variable initializers and initializer blocks of super class
  3. Constructor body of super class
  4. Static initializer of self class
  5. Instance variable initializers and initializer blocks of self class
  6. Constructor body of self class

Don't call overridden method in super class constructor. As the initialization of descendant class is behind super class, if descendant class override the method and access its fields in it, these fields don't have proper value when they are used. If someone extends your class, behavior of the program will be unpredictable.

Object Initialization in Java
Share this