In the below code snippet, the result is really confusing.
public class TestInheritance {
public static void main(String[] args) {
new Son();
Two things are going on here, let's look at them:
First of all, you are creating two different fields. Taking a look at a (very isolated) chunks of the bytecode, you see this:
class Father {
public java.lang.String x;
// Method descriptor #17 ()V
// Stack: 2, Locals: 1
public Father();
...
10 getstatic java.lang.System.out : java.io.PrintStream [23]
13 aload_0 [this]
14 invokevirtual java.io.PrintStream.println(java.lang.Object) : void [29]
17 getstatic java.lang.System.out : java.io.PrintStream [23]
20 aload_0 [this]
21 getfield Father.x : java.lang.String [21]
24 invokevirtual java.io.PrintStream.println(java.lang.String) : void [35]
27 return
}
class Son extends Father {
// Field descriptor #6 Ljava/lang/String;
public java.lang.String x;
}
Important are lines 13, 20 and 21; the others represent the System.out.println();
itself, or the implicit return;
. aload_0
loads the this
reference, getfield
retrieves a field value from an object, in this case, from this
. What you see here is that the field name is qualified: Father.x
. In the one line in Son
, you can see there is a separate field. But Son.x
is never used; only Father.x
is.
Now, what if we remove Son.x
and instead add this constructor:
public Son() {
x = "Son";
}
First a look at the bytecode:
class Son extends Father {
// Field descriptor #6 Ljava/lang/String;
public java.lang.String x;
// Method descriptor #8 ()V
// Stack: 2, Locals: 1
Son();
0 aload_0 [this]
1 invokespecial Father() [10]
4 aload_0 [this]
5 ldc [12]
7 putfield Son.x : java.lang.String [13]
10 return
}
Lines 4, 5 and 7 look good: this
and "Son"
are loaded, and the field is set with putfield
. Why Son.x
? because the JVM can find the inherited field. But it's important to note that even though the field is referenced as Son.x
, the field found by the JVM is actually Father.x
.
So does it give the right output? Unfortunately, no:
I'm Son
Father
The reason is the order of statements. Lines 0 and 1 in the bytecode are the implicit super();
call, so the order of statements is like this:
System.out.println(this);
System.out.println(this.x);
x = "Son";
Of course it's gonna print "Father"
. To get rid of that, a few things could be done.
Probably the cleanest is: don't print in the constructor! As long as the constructor hasn't finished, the object is not fully initialized. You are working on the assumption that, since the println
s are the last statements in your constructor, your object is complete. As you have experienced, this is not true when you have subclasses, because the superclass constructor will always finish before your subclass has a chance to initialize the object.
Some see this as a flaw in the concept of constructors itself; and some languages don't even use constructors in this sense. You could use an init()
method instead. In ordinary methods, you have the advantage of polymorphism, so you can call init()
on a Father
reference, and Son.init()
is invoked; whereas, new Father()
always creates a Father
object. (of course, in Java you still need to call the right constructor at some point).
But I think what you need is something like this:
class Father {
public String x;
public Father() {
init();
System.out.println(this);//[2]It is called in Father constructor
System.out.println(this.x);
}
protected void init() {
x = "Father";
}
@Override
public String toString() {
return "I'm Father";
}
}
class Son extends Father {
@Override
protected void init() {
//you could do super.init(); here in cases where it's possibly not redundant
x = "Son";
}
@Override
public String toString() {
return "I'm Son";
}
}
I don't have a name for it, but try it out. It will print
I'm Son
Son
So what's going on here? Your topmost constructor (that of Father
) calls an init()
method, which is overridden in a subclass. As all constructor call super();
first, they are effectively executed superclass to subclass. So if the topmost constructor's first call is init();
then all of the init happens before any constructor code. If your init method fully initializes the object, then all constructors can work with an initialized object. And since init()
is polymorphic, it can even initialize the object when there are subclasses, unlike with the constructor.
Note that init()
is protected: subclasses will be able to call and override it, but classes in other package won't be able to call it. That's a slight improvement over public
and should be considered for x
too.