2

I stumbled upon this piece of code.
I tried to guess what will be the result of running it before actually doing so. I was really confused when I saw them & in need of some explanations.
This is the code:

public class A {
    String bar = "A.bar";
    A() { foo(); }

    public void foo() {
        System.out.println("A.foo(): bar = " + bar);
    }
}

public class B extends A {
    String bar = "B.bar";
    B() { foo(); }
    public void foo() {
        System.out.println("B.foo(): bar = " + bar);
    }
}

public class C {
    public static void main(String[] args) {
        A a = new B();
        System.out.println("a.bar = " + a.bar);
        a.foo();
    }
}

The output is:

B.foo(): bar = null
B.foo(): bar = B.bar
a.bar = A.bar
B.foo(): bar = B.bar

Why is that?

  • How is bar = null?
  • Why is a.bar = A.bar even appear? I haven't instantiated A at all.
  • And if A appears, why is it after B?
Idos
  • 15,053
  • 14
  • 60
  • 75

1 Answers1

7

There are a few facts you ought to know before I start explaining every single step in your code's execution:

  • Field references are resolved based on reference type and method calls are resolved during run-time (in a dynamic-fashion) based on the object type.
  • super() is placed implicitly in every constructor even if you don't put it there yourself (it is not called if you call super(int x, int y) for instance).
  • It is considered very bad practice to call "override-able" methods from a constructor - you will see why when we go through the execution.

Now let's break down your code step-by-step:

  • You instantiate B by calling its default constructor B().
  • As I said before, the call to super() is added implicitly to any constructor, so A() is immediately called.
  • Inside A() you call foo(), which is overridden in class B and that is why foo() is called from B.
  • Inside B's foo() you get the output B.foo(): bar = null since Java didn't get to initialize B's fields yet (its constructor hasn't been executed yet!) and the fields of object type are initialized to null by default.
  • Now that we are done with the constructor of A() we go back to the constructor of B().
  • Inside said constructor, we have the call to foo() again, which is again B's foo(). But different from last time, B have its fields initialized (after the call to super()) properly so you get the expected B.foo(): bar = B.bar.
  • Now we are back to the warm embrace of main.
  • You access a.bar, and since as I said field references are resolved based on reference type, you get the field bar of A.
  • Lastly, for the same reason, you call a.foo() which again triggers B's foo() which prints b.bar once again.

And we are done! :)

Further references and worthwhile reading materials:
Static and dynamic binding explained
Order of constructor calls

Community
  • 1
  • 1
Idos
  • 15,053
  • 14
  • 60
  • 75