Meet The Bears. Bears implement the Bear interface and extend AbstractBear. They can all roar and in fact they do roar every time they are constructed.
public interface Bear
{
public void roar();
}
public abstract class AbstractBear implements Bear
{
public String howIRawr = "ROAR.";
public AbstractBear()
{
roar();
}
@Override
public void roar()
{
System.out.println(howIRawr);
}
}
public class GrandpaBear extends AbstractBear implements Bear
{
public GrandpaBear()
{
super();
roar();
}
@Override
public void roar()
{
System.out.println(howIRawr);
}
}And here's a very simple main method to drive them.
public class ConstructorAndBlockCallOrder
{
public static void main(String[] args)
{
Bear bear = new GrandpaBear();
}
}The output of the main method is:
ROAR.
ROAR.
GrandpaBear's overridden roar method has an identical implementation to AbstractBear's. Because GrandpaBear has no howIRawr field of his own, super.howIRawr is implicitly used instead, and so both the roars produce identical outputs.
Let's give GrandpaBear his own roar.
public class GrandpaBear extends AbstractBear implements Bear
{
// Actually, GrandpaBear got his own roar.
public String howIRawr = "ROARRRMNOMnomnomnommmZZZzzz....";
public GrandpaBear()
{
super();
roar();
}
@Override
public void roar()
{
System.out.println(howIRawr);
}
}The output of the main method is now:
null
ROARRRMNOMnomnomnommmZZZzzz....
The null is what confused me. The null is a result of the call to super(), which in turn calls roar(). I assumed that the roar() called would be AbstractBear#roar(), because it is called from the constructor of AbstractBear. I told myself that the roar() was really this.roar() which given the scope would surely be AbstractBear#roar(). That incorrect assumption led me to a flawed theory that perhaps the scope of fields is taken from the runtime type even if the logic is not. Totally bonkers...
The assumption was incorrect because I misunderstood what this meant in that situation. This does not refer to the declared type of the method being called, it refers to the runtime type of the current instance. So although the this.roar() is declared in AbstractBear, it's actually referring to GrandpaBear, because that is the actual type declared in the main method.
In retrospect it seems incredibly obvious, because the entire point of overriding is so a subclass can use its own implementation of a method, even if it's being used as one of its super types.
So the reason the super() prints null is because it calls GrandpaBear#roar() which prints the value of GrandpaBear.howIRawr which is null at the time roar() is called.
The howIRawr is null because the declaration where it is instantiated has not been reached yet. AbstractBear must be instantiated before GrandpaBear because it GrandpaBear's supertype, which is also why the compiler complains if super() is not the first declaration in a subclass' constructor. So during the call the execution of super() none of the instance members of and subclass' are available.
Notice how I said 'instance' members. We can fix the null above by making GrandpaBear's howIRawr static.
public class GrandpaBear extends AbstractBear implements Bear
{
// Correction, 'static' roar.
public static String howIRawr = "ROARRRMNOMnomnomnommmZZZzzz....";The output of the main method is now:
ROARRRMNOMnomnomnommmZZZzzz....
ROARRRMNOMnomnomnommmZZZzzz....
Because the field is static it is loaded when the class is loaded, before any constructors for that class are ever called.
The 'static' fix makes me wonder what the exact order of loading is when the subclass of something is instantiated, I speculate that it must be:
- Load static members of classes in type hierarchy from super to sub:
Object->AbstractBear->GrandpaBear. - Load instance variables from top class in the hierarchy. (Before constructor calls because instance variable values are available in constructors.)
- Call constructor from top class in hierarchy.
- Repeat steps 2-4 for all classes descending down the type hierarchy one at a time.
If we follow this order we can see the instance variables for GrandpaBear are not loaded when the call to AbstractBear's constructor is made.
AbstractBearstatic members loaded...GrandpaBearstatic members loaded...AbstractBearinstance variables loaded...AbstractBearconstructor called... (i.e.super()inGrandpaBear's constructor.)GrandpaBear's instance variables loaded... (too little too late,howIRawrwas already printed asnull.)