Reputation: 693
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
Upvotes: 8
Views: 512
Reputation: 86409
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:
"3. This constructor does not begin with an explicit constructor invocation of another constructor in the same class (using this). If this constructor is for a class other than Object, then this constructor will begin with an explicit or implicit invocation of a superclass constructor (using super). Evaluate the arguments and process that superclass constructor invocation recursively using these same five steps. If that constructor invocation completes abruptly, then this procedure completes abruptly for the same reason. Otherwise, continue with step 4.
"4. Execute the instance initializers and instance variable initializers for this class, assigning the values of instance variable initializers to the corresponding instance variables, in the left-to-right order in which they appear textually in the source code for the class. If execution of any of these initializers results in an exception, then no further initializers are processed and this procedure completes abruptly with that same exception. Otherwise, continue with step 5."
Upvotes: 1
Reputation: 48827
boolean bool = true;
public ClassTwo() {
super();
}
is identical to
boolean bool;
public ClassTwo() {
super();
bool = true;
}
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
, when super()
is called (and thus ClassOne()
and fireMethod()
), bool
hasn't been set to true
yet.
Fun fact: the following constructor
public ClassTwo() {
super();
fireMethod();
}
will be understood as
public ClassTwo() {
super();
bool = true;
fireMethod();
}
by the JVM, and the output will thus be
bool=false
bool=true
Upvotes: 7
Reputation: 109567
The final answer will be: do not use an overridable method in a constructor.
In every constructor:
type field = value;
)This makes life interesting
public class A {
public A() {
init();
}
protected void init() {
}
}
public class B extends A {
int a = 13;
int b;
@Override
protected void init() {
System.out.println("B.init a=" + a + ", b=" + b);
a = 7;
b = 15;
}
public static void main(String[] args) {
new B().f();
}
public void f() {
System.out.println("B.f a=" + a + ", b=" + b);
}
}
This results in
B.init a=0, b=0
B.f a=13, b=15
a
to 7 (for nothing), and b
to 15.a
is initialized.My IDE already flags calling an overridable method in a constructor as bad style.
Upvotes: 0
Reputation: 95528
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 of ClassOne
is called first. So before a constructor is run, the instance members have their default values (so bool
is false
). It is only after the constructors are run, that the fields are initialized.
So your constructor effectively becomes:
public ClassTwo() {
super(); //call constructor of super class
bool = true; //initialize members;
}
But ClassOne
calls the overridable method that prints out the value of bool
, which is false
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):
There are a few more restrictions that a class must obey to allow inheritance. Constructors must not invoke overridable methods, directly or indirectly. If you violate this rule, program failure will result. The superclass constructor runs before the subclass constructor, so the overriding method in the subclass will be invoked before the subclass constructor has run. If the overriding method depends on any initialization performed by the subclass constructor, the method will not behave as expected.
Upvotes: 0
Reputation: 328649
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.
Upvotes: 5