Are fields initialized before constructor code is run in Java?
Asked Answered
G

5

85

Can anyone explain the output of following program? I thought constructors are initialized before instance variables. So I was expecting the output to be "XZYY".

class X {
    Y b = new Y();

    X() {
        System.out.print("X");
    }
}

class Y {
    Y() {
        System.out.print("Y");
    }
}

public class Z extends X {
    Y y = new Y();

    Z() {
        System.out.print("Z");
    }

    public static void main(String[] args) {
        new Z();
    }
}
Gourami answered 11/2, 2013 at 3:41 Comment(1)
TL;DR: The new's in the class member initializations are implicitly put at the top of the constructors.Knightly
P
138

The correct order of initialisation is:

  1. Static variable initialisers and static initialisation blocks, in textual order, if the class hasn't been previously initialised.
  2. The super() call in the constructor, whether explicit or implicit.
  3. Instance variable initialisers and instance initialisation blocks, in textual order.
  4. Remaining body of constructor after super().

See sections §2.17.5-6 of the Java Virtual Machine Specification.

Prader answered 11/2, 2013 at 5:21 Comment(8)
Link to JLS for Java 8: docs.oracle.com/javase/specs/jls/se8/html/jls-12.html#jls-12.5Quiroz
And also wrong - see my comment to @ÓscarLópez with same issue. Note that static is simply not part of the documentation section you are referring.Bainite
The other article with the comment has been deleted - but I supplied the specifics in a supplementary answer with sample code. Not a complete answer - but at least highlights the static {} case.Bainite
@Bainite This answer agrees with the JLS: specifically, in respect of the execution you mention in your answer, with §12.4.2 item 9. If your implementation doesn't comply with the JLS, report it as a bug.Prader
What confuses is that class and instance instantiation is discussed separately. I guess that leaves implementation specifics open in how the two will intermix.Bainite
It's worth pointing out that the "in textural order" in step 3 applys to interleaved order of both instance initialisation blocks(IIB) and Instance variable initialiser(IVB) which means there can be the execution order such as IIB1, IVB1, IIB2, IVB2.Ballplayer
@Bainite Not at all. The class has to be fully initialized before the instance can be created. Surely this is obvious?Prader
See my answer - I was able to create an instance before full static initialization. All I had to do is create the instance in a static context, kind of the same way enum values are created.Bainite
B
75

If you look at the decompiled version of the class file

class X {
    Y b;

    X() {
        b = new Y();
        System.out.print("X");
    }
}

class Y {
    Y() {
        System.out.print("Y");
    }
}

public class Z extends X {

    Y y;

    Z() {
        y = new Y();
        System.out.print("Z");
    }

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

You can find that the instance variable y is moved inside the constructor, so the execution sequence is as follows

  1. Call the constructor of Z
  2. It triggers the default constructor of X
  3. First line of X constructor new Y() is called.
  4. Print Y
  5. Print X
  6. Call the first line in constructor Z new Y()
  7. Print Y
  8. Print Z

All the instance variables are initialized by using constructor statements.

Bowler answered 11/2, 2013 at 3:47 Comment(4)
Great explanation @Arun P Johhny +1 for you.Muckraker
The initialization of the instance variable is moved inside the constructor.Prader
At least not another wrong interpretation of the spec. It is incomplete but helps explaining what happens.Bainite
Why don't I see a super() call if it's decompiled code?Teirtza
J
6

When you invoke a constructor, the instance variable initializers run before the body of the constructor. What do you think the output of the below program ?

public class Tester {
    private Tester internalInstance = new Tester();
    public Tester() throws Exception {
        throw new Exception("Boom");
    }
    public static void main(String[] args) {
        try {
            Tester b = new Tester();
            System.out.println("Eye-Opener!");
        } catch (Exception ex) {
            System.out.println("Exception catched");
        }
    }
}

The main method invokes the Tester constructor, which throws an exception. You might expect the catch clause to catch this exception and print Exception catched. But if you tried running it, you found that it does nothing of that sort and It throws a StackOverflowError.

Journalism answered 6/8, 2018 at 18:21 Comment(1)
So you are basically trying to show that the constructor will keep on invoking itself (because of the line private Tester internalInstance = new Tester();) before the control comes to the throw section. Is my understanding correct?Mahan
B
2

To clarify the misconceptions with static - I will simply refer to this small piece of code:

public class Foo {

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

    static {
        System.out.println("Static Block 1");
    }

    public static final Foo FOO = new Foo();

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

    static {
        System.out.println("Static Block 2 (Weird!!)");
    }

    public Foo() {
        System.out.println("Constructor");
    }

    public static void main(String[] args) {
        System.out.println("In Main");
        new Foo();
    }
}

Surprise is that the output is as follows:

Static Block 1
Instance Block 1
Instance Block 2
Constructor
Static Block 2 (Weird!!)
In Main
Instance Block 1
Instance Block 2
Constructor

Note that we have a static {} that is called after two instance {}. this happens because we throw in the constructor in the middle, interjecting execution order the first time the constructor is called.

Discovered this when I was working on this answer - https://mcmap.net/q/99153/-static-initialization-blocks.

Basically we observe this to happen:

  1. During the first time an object is initialized, Initialize current object for both static and instance initialization intermixed based on order of occurrence

  2. For all next initializations, only do the instance initialization in the order of occurrence, as static initialization already happened.

I need to research how the mix of inheritance, and both explicit and implicit calls to super and this will affect this, and will update with findings. It would be likely similar to the other supplied answers, except that they got it wrong with the static initialization.

Bainite answered 18/2, 2018 at 3:27 Comment(2)
This is because of private static Foo instance = new Foo();, which runs before the 2nd static block. In fact it is the 2nd static block, and what you have labelled 2nd is really 3rd. No mystery here.Prader
I think the takeaway here is to always make sure that static fields containing instantiations of the class they're written in, are the last static initializations within the class. For example, wrong is: class Color { static final Color RED = new Color(255,0,0); static { ... } }, right is: class Color { static { ... } static final Color RED = new Color(255,0,0); }.Normanormal
S
1

Observe the code:

public class CodeBlockSeq {
  static int k = 0;
  static protected String log(Class<?> cls, String message) {
    return log(cls, "static", message);
  }
  static protected String log(Class<?> cls, String name, String message) {
    System.out.println(k++ +":"+  cls.getSimpleName() +':'+ name +':'+ message );
    return message;
  }

  static public class Hello {
    final protected String name;

    private static String s0 = CodeBlockSeq.log(Hello.class,"var 0");
    private String a0 = log(Hello.class,"var 0");
    static {
      CodeBlockSeq.log(Hello.class, "block 0");
    }
    private static String s1 = CodeBlockSeq.log(Hello.class,"var 1");
    private String a1 = log(Hello.class, "var 1");
    {
      log(Hello.class, "init block 1");
    }

    public Hello(String name) {
      this.name = name;
      log(Hello.class,"Constructor\n");
    }
    private static String s2 = CodeBlockSeq.log(Hello.class,"var 2");
    private String a2 = log(Hello.class, "var 2");
    {
      log(Hello.class, "init block 2");
    }
    static {
      CodeBlockSeq.log(Hello.class, "block 2");
    }

    private static String s3 = CodeBlockSeq.log(Hello.class,"var 3");
    private String a3 = log(Hello.class, "var 3");
    {
      log(Hello.class, "init block 3");
    }

    private static String s4 = CodeBlockSeq.log(Hello.class, "var 4");

    protected String log(Class<?> cls, String message) {
      return CodeBlockSeq.log(cls, "Hello-"+name, message);
    }

  }

  static public class Bello extends Hello {
//  static private Hello s0 = new Hello("jello");
    private static String s0 = CodeBlockSeq.log(Bello.class,"var 0");
    private String a0 = log(Bello.class, "var 0");
    static {
      CodeBlockSeq.log(Bello.class, "block 0");
    }
    private static String s1 = CodeBlockSeq.log(Bello.class,"var 1");
    private String a1 = log(Bello.class, "var 1");
    {
      log(Bello.class, "init block 1");
    }

    public Bello(String name) {
      super(name);
      log(Bello.class, "Constructor\n");
    }

    private static String s2 = CodeBlockSeq.log(Bello.class,"var 2");
    private String a2 = log(Bello.class, "var 2");
    {
      log(Bello.class, "init block 2");
    }
    static {
      CodeBlockSeq.log(Bello.class, "block 2");
    }

    private static String s3 = CodeBlockSeq.log(Bello.class,"var 3");
    private String a3 = log(Bello.class, "var 3");
    {
      log(Bello.class, "init block 3");
    }

    private static String s4 = CodeBlockSeq.log(Bello.class,"var 4");
    public String getName() {
      return this.name;
    }
    protected String log(Class<?> cls, String message) {
      return null;
    }
  }

  @Test
  public void testIt() {
    Hello x = new Bello("pollo") {
      private String a1 = log(this.getClass(), "var 1");
      {
        log(this.getClass(), "init block 1");
      }
      private String a2 = log(this.getClass(), "var 2");
      {
        log(this.getClass(), "init block 2");
      }
      private String a3 = log(this.getClass(), "var 3");
      {
        log(this.getClass(), "init block 3");
      }

      protected String log(Class<?> cls, String message) {
        return CodeBlockSeq.log(cls, "pollo-"+name, message);
      }

    };
  }
}

Running the test would produce:

0:Hello:static:var 0
1:Hello:static:block 0
2:Hello:static:var 1
3:Hello:static:var 2
4:Hello:static:block 2
5:Hello:static:var 3
6:Hello:static:var 4
7:Bello:static:var 0
8:Bello:static:block 0
9:Bello:static:var 1
10:Bello:static:var 2
11:Bello:static:block 2
12:Bello:static:var 3
13:Bello:static:var 4
14:Hello:pollo-null:var 0
15:Hello:pollo-null:var 1
16:Hello:pollo-null:init block 1
17:Hello:pollo-null:var 2
18:Hello:pollo-null:init block 2
19:Hello:pollo-null:var 3
20:Hello:pollo-null:init block 3
21:Hello:pollo-pollo:Constructor

22:Bello:pollo-pollo:var 0
23:Bello:pollo-pollo:var 1
24:Bello:pollo-pollo:init block 1
25:Bello:pollo-pollo:var 2
26:Bello:pollo-pollo:init block 2
27:Bello:pollo-pollo:var 3
28:Bello:pollo-pollo:init block 3
29:Bello:pollo-pollo:Constructor

30::pollo-pollo:var 1
31::pollo-pollo:init block 1
32::pollo-pollo:var 2
33::pollo-pollo:init block 2
34::pollo-pollo:var 3
35::pollo-pollo:init block 3

Notice that they are run in this order:

  1. static blocks and objects of parent class
    • in the order that they are declared.
  2. static blocks and objects of extension class
    • in the order that they are declared.
  3. instance blocks and objects of parent class
    • in the order that they are declared.
  4. parent constructor
  5. instance blocks and objects of extension class
    • in the order that they are declared.
  6. extension class constructor
  7. instance blocks and objects of anonymous extension class
    • in the order that they are declared.

Notice that:

//  static private Hello s0 = new Hello("jello");
private static String s0 = CodeBlockSeq.log(Bello.class,"var 0");

Is a static object that is printed as the first line.

What if replace that with:

static private Hello s0 = new Hello("jello");
//  private static String s0 = CodeBlockSeq.log(Bello.class,"var 0");

So that it will print in this order

  1. static blocks and objects of parent class

    • in the order that they are declared.
  2. Then the first static member of extension class Bello

    • 2.1 static blocks and objects of jello's static class

      • But has already run in step 1, so was not done again.
    • 2.2 instance blocks and objects of jello instance

      • in the order that they are declared.
    • 2.3 constructor for jello instance

    • 2.4 The rest of the static blocks and objects of extension class

      • in the order that they are declared.

Just as it would have done for static String s0.

Then

  1. instance blocks and objects of parent class
    • in the order that they are declared.
  2. parent constructor
  3. instance blocks and objects of extension class
    • in the order that they are declared.
  4. extension class constructor
  5. instance blocks and objects of anonymous extension class
    • in the order that they are declared.

0:Hello:static:var 0
1:Hello:static:block 0
2:Hello:static:var 1
3:Hello:static:var 2
4:Hello:static:block 2
5:Hello:static:var 3
6:Hello:static:var 4
7:Hello:Hello-null:var 0
8:Hello:Hello-null:var 1
9:Hello:Hello-null:init block 1
10:Hello:Hello-null:var 2
11:Hello:Hello-null:init block 2
12:Hello:Hello-null:var 3
13:Hello:Hello-null:init block 3
14:Hello:Hello-jello:Constructor

15:Bello:static:block 0
16:Bello:static:var 1
17:Bello:static:var 2
18:Bello:static:block 2
19:Bello:static:var 3
20:Bello:static:var 4
21:Hello:pollo-null:var 0
22:Hello:pollo-null:var 1
23:Hello:pollo-null:init block 1
24:Hello:pollo-null:var 2
25:Hello:pollo-null:init block 2
26:Hello:pollo-null:var 3
27:Hello:pollo-null:init block 3
28:Hello:pollo-pollo:Constructor

29:Bello:pollo-pollo:var 0
30:Bello:pollo-pollo:var 1
31:Bello:pollo-pollo:init block 1
32:Bello:pollo-pollo:var 2
33:Bello:pollo-pollo:init block 2
34:Bello:pollo-pollo:var 3
35:Bello:pollo-pollo:init block 3
36:Bello:pollo-pollo:Constructor

37::pollo-pollo:var 1
38::pollo-pollo:init block 1
39::pollo-pollo:var 2
40::pollo-pollo:init block 2
41::pollo-pollo:var 3
42::pollo-pollo:init block 3

Just because the static object s0 changed from String to Hello class, the java runtime does not have built-in intelligence to, make an exception for and then, relegate s0 to the back of the execution queue. It does not matter what class s0 is.

Shavers answered 30/10, 2022 at 12:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.