28

In an interview I was given the following code:

public abstract class Base {
    public int x = 1;
    public Base() {
        foo();
    }
    public abstract void foo();
}

public class Derived extends Base {
    int x = 2;
    @Override
    public void foo() {
        System.out.println("Derived: "+x);
    }
}

class Main {
    public static void main(String... args) {
        Base base = new Derived();
        base.foo();
    }
}

They asked:

What will be printed?

If we were using C++ I think the code should give a compilation error because when the Derived constructor is called first the constructor of the Base class is called. At this point the foo method doesn't exist.

In addition I know that first the inherited class constructor is called, before all the variables is created.

However in Java we get:

Derived: 0
Derived: 2

Why?

I know that like in C++ Java inheritance is based always on virtual tables, and the constructor of the Base class is called before the constructor of the Derived class.

Tom Anderson
  • 46,189
  • 17
  • 92
  • 133
user1247968
  • 281
  • 3
  • 3
  • 4
    java is not based on virtual tables, it has "objects layers" implementation for dynamic dispatching – amit Mar 04 '12 at 11:10
  • 2
    In C++ this would just be [undefined behaviour](http://stackoverflow.com/q/9529083/596781). – Kerrek SB Mar 04 '12 at 11:14
  • 2
    The main class you've shown is illegal. You can't write `base.foo()` there. – Flexo Mar 04 '12 at 11:15
  • In C++ terminiology, all Java variables are either fundamental numeric types or pointers, and thus they can (and will) always be zero-initialized, and only later *assigned* to their "initial value". Thus there is no need for initializer lists, and *every* class is default-constructible, if you will. – Kerrek SB Mar 04 '12 at 11:17
  • 1
    @awoodland: I assume the OP intended that to be a main method, so i've added one. – Tom Anderson Mar 04 '12 at 12:11
  • The JVM can optimise the virtual table away, so they are not always used. e.g. if there is only one implementation used. – Peter Lawrey Mar 04 '12 at 16:28

2 Answers2

28

This is the order in which the code is executed. More details follow.

  • main()
    • invokes Derived.<init>() (the implicit nullary constructor)
      • invokes Base.<init>()
        • sets Base.x to 1.
        • invokes Derived.foo()
          • prints Derived.x, which still has the default value of 0
      • sets Derived.x to 2.
    • invokes Derived.foo().
      • prints Derived.x, which is now 2.

To completely understand what is going on, there are several things you need to know.

Field Shadowing

Base's x and Derived's x are completely different fields which happen to have the same name. Derived.foo prints Derived.x, not Base.x, since the latter is "shadowed" by the former.

Implicit Constructors

Since Derived has no explicit constructor, the compiler generates an implicit zero-argument constructor. In Java, every constructor must call one superclass constructor (with the exception of Object, which has no superclass), which gives the superclass a chance to safely initialize its fields. A compiler-generated nullary constructor simply calls the nullary constructor of its superclass. (If the superclass has no nullary constructor, a compilation error is produced.)

So, Derived's implicit constructor looks like

public Derived() {
    super();
}

Initializer Blocks and Field Definitions

Initializer blocks are combined in declaration order to form a big block of code which is inserted into all constructors. Specifically, it is inserted after the super() call but before the rest of the constructor. Initial value assignments in field definitions are treated just like initializer blocks.

So if we have

class Test {
    {x=1;}
    int x = 2;
    {x=3;}

    Test() {
        x = 0;
    }
}

This is equivalent to

class Test {
    int x;

    {
        x = 1;
        x = 2;
        x = 3;
    }

    Test() {
        x = 0;
    }
}

And this is what the compiled constructor will actually look like:

Test() {
    // implicit call to the superclass constructor, Object.<init>()
    super();
    // initializer blocks, in declaration order
    x = 1
    x = 2
    x = 3
    // the explicit constructor code
    x = 0
}

Now let's return to Base and Derived. If we decompiled their constructors, we would see something like

public Base() {
    super(); // Object.<init>()
    x = 1; // assigns Base.x
    foo();
}

public Derived() {
    super(); // Base.<init>()
    x = 2; // assigns Derived.x
}

Virtual Invocations

In Java, invocations of instance methods normally go through virtual method tables. (There are exceptions to this. Constructors, private methods, final methods, and methods of final classes cannot be overridden, so these methods can be invoked without going through a vtable. And super calls do not go through vtables, since they are inherently not polymorphic.)

Every object holds a pointer to a class handle, which contains a vtable. This pointer is set as soon as the object is allocated (with NEW) and before any constructors are called. So in Java, it is safe for constructors to make virtual method calls, and they will be properly directed to the target's implementation of the virtual method.

So when Base's constructor calls foo(), it invokes Derived.foo, which prints Derived.x. But Derived.x hasn't been assigned yet, so the default value of 0 is read and printed.

Daniel Lubarov
  • 7,796
  • 1
  • 37
  • 56
10

Obviously, only the derived class's foo() is called.

It prints 0 in the first time because it happens before assigning x = 2, which happens only in the constructor of Derived, after Base's initialization is complete. It prints 0 and not 1, because Derived.x is being accessed and not Base.x, and it was not initialized yet, and is still 0. The declaration of x in Derived hides the field in Base, so when Derived is printing x, it prints Derived.x.

EDIT: activation order when creating Derived(): [schematic]

1. create Base:
   1.1. assign Base.x = 1
   1.2. invoke foo()
      1.2.1 print Derived: Derived.x //Derived.x was not initialized here yet!
2. assign Derived.x = 2

The second is trivial and expected [in my opinion at least].

amit
  • 175,853
  • 27
  • 231
  • 333
  • 1
    I have never heard of "object's layers". Whether a Java implementation uses virtual tables is entirely up to the implementation. I believe that most implementations do use vtables. Could you expand on your statement to the contrary? – Tom Anderson Mar 04 '12 at 12:13
  • @TomAnderson: I cannot recall the exact terminology for "object layers" but the idea is that each object is an instance of a class, and the class itself is an object [which is instance of `Class`]. The methods are not stored in a virtual table, but rather in the `Class` object. You can see strong indication for that when you use reflection. One of the reasons C++ does not have reflection is because it [C++] does not have layers model. Some languages such as smalltalk even have more complex model, using meta-class as well. – amit Mar 04 '12 at 12:38
  • 1
    @amit I'm also confused about what you mean. HotSpot certainly uses vtables, see [runtime_resolve_virtual_method](http://hg.openjdk.java.net/jdk7/hotspot-rt/hotspot/file/5dce25362b8a/src/share/vm/interpreter/linkResolver.cpp) which reads `vtable_index`. You're right, objects have pointers to "class handles" (in HotSpot terminology). And `Class` has a bunch of native methods, the implementations of which read from these class handles. Is there a JDK which doesn't use vtables? – Daniel Lubarov Mar 04 '12 at 12:55
  • 1
    From what i remember of the Sun JDK's memory layout, objects have a pointer to a 'klass', which is basically a vtable with a few extra pointers to other class-level information, for GC and so on. The `Class` object you see in Java is something else, a sort of proxy to the klass. See the [HotSpot glossary](http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html#klassPointer). – Tom Anderson Mar 04 '12 at 13:16
  • @TomAnderson: I could be wrong, but I think you were right and it is implementation specific issue. I'll delete the last statement. – amit Mar 04 '12 at 13:20