0

i got a question in my Java test and i was wrong. please explain to me why.

public class A {
    protected String str = "A";
    public static int i = 1;
    public A() {
        foo();
    }
    public void foo() {
        // TODO Auto-generated method stub
        System.out.println(str + " " + i);
    }
}
public class B extends A {
    protected String str = "B";
    public static int i = 2;
    public B() {
        foo();
    }
    public void foo() {
        // TODO Auto-generated method stub
        System.out.println(str + " " + i);

    }
}
public class Program {
    public static void main(String args[]){

                A a = new B();
                B b = new B();
    }
}

in the test they asked me what is the output? i answered : "A 1 B 2 A 1 B 2" but the correct answer was : "null 2 B 2 null 2 B 2" could you please explain why?

Meni
  • 89
  • 1
  • 9

2 Answers2

0

For the line A a = new B(); the output is null 2 B 2, because the instance fields in your class are not initialized until the implicit super() is finished running.

In this case since class B is extending the class A the B() constructor will implicitly call the super class no-arg constructor A() through super(), where the foo() method is called.

public A() {                                            <---
    foo(); //overriden version in class B is called        |
}                                                          |
                                                           |
public B() {                                               |
    // Static fields are already initialized               |
    // Instance fields are not yet initialized             |
    // Here super() is called implicitly which calls A() ---
    // Instance fields are now initialized to respective values
    foo();
}

The overriden foo() of class B is called and as the foo() is overriden in class B, now since the super() is not yet finished the instance fields of class B is not initialized and is null. So you see the first null 2.

Then when the control comes out of super() the instance initializers are run for B and then foo() of class B is called at which point the instance fields of class B is initialized to the value B, so you see B 2 printed.

You don't see any issue for the static fields as because they don't depend on the super() call to finish, they are initialized when the class is loaded, so you see the value of i always initialized and printed instead of null.

Same is the case for the line B b = new B();.

An important thing to note is that you have redefined the str instance field in class B, so when the overriden foo() is invoked it will actually refer to the instance field of the class B rather than the field of class A.

Consider this code, where I removed the str field from class B:

class A {
    protected String str = "A";
    public static int i = 1;
    public A() {
        foo();
    }
    public void foo() {
        // TODO Auto-generated method stub
        System.out.println(str + " " + i);
    }
}
 class B extends A {
    public static int i = 2;
    public B() {
        foo();
    }
    public void foo() {
        // TODO Auto-generated method stub
        System.out.println(str + " " + i);

    }
}

This will print the output below when you re-run your program:

A 2
A 2
A 2
A 2

This is happening because the super() of B is calling the constructor A() which in turn is calling the Object() constructor through its own super(). Once the super() is done in A() the instance members of B inherited from A are all initialised. Now the overriden foo() will print out the initialised value of str inherited from A.

Fullstack Guy
  • 16,368
  • 3
  • 29
  • 44
0

new B() will attempt to create a new instance of B, using B's constructor. All constructors always start with a super() call even if said call isn't there. Separate from that there are the 'instance initializers'; these are the expressions being assigned to the str variable. They are not string constants because that'd require a static final variable and str isn't.

Methods overload, but fields do not: Each class has its own field named str unrelated to the other one's str field.

The order of execution for object creation is as follows:

  1. First, set all fields to initial value (null / 0 / false)
  2. Run the super() call from the targeted constructor in B to figure out which constructor is intended for A(), along with the parameters required to call the constructor of the parent.
  3. Invoke that superconstructor (which itself does this 5-step routine on its own stuff too!)
  4. run all instance initializers in this class.
  5. now continue and run the rest of the constructor.

So, in order:

  1. A.str, B.str are both set to null.
  2. A.str = "A" is evaluated.
  3. A's constructor runs, and calls foo(). foo is a method, so dynamic dispatch is used; B overrides it, this is an instance of B, therefore, run B's foo(), not A's foo().
  4. B's foo runs, and prints B's str which is still null.
  5. B's foo prints its version of 'i', which is 2. Hence null 2
  6. Now B.str = "B" is evaluated.
  7. now the rest of B's constructor is run, which again runs B's foo(), but this time B.str has been initialized, so it prints B 2 instead.
  8. Then we do it again; the type of the variable you assign the instance to has absolutely no effect here, so, null 2 B 2 prints a second time.
rzwitserloot
  • 85,357
  • 5
  • 51
  • 72