I must have spent over an hour trying to figure out the reason for some unexpected behavior. I ended up realizing that a field wasn't being set as I'd expect. Before shrugging and moving on, I'd like to understand why this works like this.
In running the example below, I'd expect the output to be true, but it's false. Other tests show that I always get whatever that type default value is.
public class ClassOne {
public ClassOne(){
fireMethod();
}
protected void fireMethod(){
}
}
public class ClassTwo extends ClassOne {
boolean bool = true;
public ClassTwo() {
super();
}
@Override
protected void fireMethod(){
System.out.println("bool="+bool);
}
public static void main(String[] args) {
new ClassTwo();
}
}
output:
bool=false
The final answer will be: do not use an overridable method in a constructor.
In every constructor:
type field = value;
)This makes life interesting
This results in
a
to 7 (for nothing), andb
to 15.a
is initialized.My IDE already flags calling an overridable method in a constructor as bad style.
super()
is actually superfluous (pun not intended) in this case because it is implicitly called in every constructor. So what this means is that the constructor ofClassOne
is called first. So before a constructor is run, the instance members have their default values (sobool
isfalse
). It is only after the constructors are run, that the fields are initialized.So your constructor effectively becomes:
But
ClassOne
calls the overridable method that prints out the value ofbool
, which isfalse
at that point.In general, it is bad practice to call overridable methods from a constructor (as you are doing in
ClassOne
) because you're now working with an object that is not completely initialized.From Effective Java (2nd Edition):
is identical to
The compiler automatically moves the fields initializations within the constructor (just after the super constructor call, implicitly or explicitly).
Since a boolean field default value is
false
, whensuper()
is called (and thusClassOne()
andfireMethod()
),bool
hasn't been set totrue
yet.Fun fact: the following constructor
will be understood as
by the JVM, and the output will thus be
The superclass constructor is called before the subclass constructor. And in Java, before a constructor is run, all instance members have their default value (false, 0, null). So when
super()
is called,bool
is still false (default value for booleans).More generally, calling an overridable method from a constructor (in ClassOne) is a bad idea for the reason you just discovered: you might end up working on an object that has not been fully initialised yet.
The instance initializers are executed after super() is called implicitly or explicitly.
From the Java Language Specification, section 12.5: "Creation of new class instances: