javainheritancemethodsconstructoroverriding

Subclass calls supers constructor which calls subclass method instead of its own


I'll start with a code example:

class A {
    public A() {
        f(); //When accessed through super() call this does not call A.f() as I had expected.
    }

    public void f() {} //I expect this to be called from the constructor.
}

class B extends A {
    private Object o;

    public B() {
        super();
        o = new Object(); //Note, created after super() call.
    }

    @Override
    public void f() {
        //Anything that access o.
        o.hashCode(); //Throws NullPointerException.
        super.f();
    }
}

public class Init {
    public static void main(String[] args) {
        B b = new B();
    }
}

This program throws a NullPointerException. When the object b enters the constructor of its superclass A and makes a call to the method f() which is overridden by the class B B.f() is called, instead of A.f() which I would have expected.

I thought a superclass wasn't supposed to know if it was subclassed or not but surely this can be used by a class to tell if it's been subclassed or not? What's the reason behind this? Is there any workaround if I really want A.f() to be called instead of B.f()?

Thanks in advance.


Follow up question:

Thanks for the answers. I now understand why it is like it is, but here's a follow up question. Perhaps I'm mistaken but a principle of subtyping is that the supertype should not know that it's been subtyped. This "mechanism" lets a class know if it's been subclassed or not. Consider this code example:

class A {
    private boolean isSubclassed = true;

    public A() {
        f(); //A.f() unsets the isSubclassed flag, B.f() does not.
        if(this.isSubclassed) {
            System.out.println("I'm subclassed.");
        } else {
            System.out.println("I'm not subclassed.");
        }
    }

    public void f() {
        this.isSubclassed = false;
    }
}

class B extends A {
    public B() {
        super();
    }

    @Override
    public void f() {}
}

public class Init {
    public static void main(String[] args) {
        new B(); //Subclass.
        new A();
    }
}

The output of this program is:

I'm subclassed.
I'm not subclassed.

Here A knows it's been subclassed. It's unkown by whom but that doesn't matter. How is this explained? Am I misinformed?


Solution

  • The NullPointerException occurs because when you construct an instance of B, the parent constructor (in A) is invoked. This constructor calls the f() method but since the actual type of the object is B, the overridden f() method in B is invoked.

    @Override
    public void f() {
        //Anything that access o.
        o.hashCode(); //Throws NullPointerException as o has not been initialised yet
        super.f();
    }
    

    The lesson here is never to call a method in a constructor which can be overridden by a subclass.