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.
A vtable is simply an array of function pointers. The slots in the array correspond to the methods of of a class. Since methods are fixed per class, we do not have to maintain an array of functions for each object. Rather, it suffices to declare one vtable array per class.
Yes, abstract classes do have vtables, also with pure abstract methods (these can actually be implemented and called), and yes - their constructor does initialize the pure entries to a specified value.
The key difference, from a conceptual standpoint, is that Java methods are "virtual" unless declared "final". In C++ the keyword "virtual" must be given explicitly for the function to be in the vtable. Anything not in vtable will be dispatched using the compile-time types rather than the runtime type of the object.
Typically, the compiler creates a separate virtual method table for each class. When an object is created, a pointer to this table, called the virtual table pointer, vpointer or VPTR, is added as a hidden member of this object.
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].
This is the order in which the code is executed. More details follow.
main()
Derived.<init>()
(the implicit nullary constructor) Base.<init>()
Base.x
to 1
.Derived.foo()
Derived.x
, which still has the default value of 0
Derived.x
to 2
.Derived.foo()
. Derived.x
, which is now 2
.To completely understand what is going on, there are several things you need to know.
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.
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 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 }
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With