Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save homostellaris/f99999074f0d1a595ca0 to your computer and use it in GitHub Desktop.

Select an option

Save homostellaris/f99999074f0d1a595ca0 to your computer and use it in GitHub Desktop.
An investigation into the meaning of 'this' and class load order.

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....

Confusion Over the Meaning of 'This'

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.

The Truth Behind the Null

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.

Class Load Order

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:

  1. Load static members of classes in type hierarchy from super to sub: Object -> AbstractBear -> GrandpaBear.
  2. Load instance variables from top class in the hierarchy. (Before constructor calls because instance variable values are available in constructors.)
  3. Call constructor from top class in hierarchy.
  4. 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.

  1. AbstractBear static members loaded...
  2. GrandpaBear static members loaded...
  3. AbstractBear instance variables loaded...
  4. AbstractBear constructor called... (i.e. super() in GrandpaBear's constructor.)
  5. GrandpaBear's instance variables loaded... (too little too late, howIRawr was already printed as null.)
public abstract class AbstractBear implements Bear
{
public String howIRawr = "ROAR.";
public AbstractBear()
{
roar();
}
@Override
public void roar()
{
System.out.println(howIRawr);
}
}
public interface Bear
{
public void roar();
}
public class GrandpaBear extends AbstractBear implements Bear
{
public GrandpaBear()
{
super();
roar();
}
@Override
public void roar()
{
System.out.println(howIRawr);
}
}
public class BearGod
{
public static void main(String[] args)
{
Bear bear = new GrandpaBear();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment