Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Considering the order of instanciation when inheriting from a base class, how does Java know when to override a class method?

I am taking an introduction to OOP (Java) in university and encountered the following code:

// This example program aims at inspecting and understanding the order in which the individual 
// initialization steps are taken when executing this program.

public class ConstructorTest {
    public static void main(String[] args) {
        new Sub();
    }
}

class Super {
    Super() {
        System.out.println("Super constructor");
        m();
    }

    void m() {
        System.out.println("Method m() of class Super");
    }
}

class Sub extends Super {
    Person p = new Person();

    Sub() {
        System.out.println("Sub constructor");
        m();
    }

    @Override
    void m() {
        System.out.println("Method m() of class Sub");
        System.out.println(p.getName());
    }
}

class Person {
    String name;

    String getName() {
        return "John Doe";
    }
}

The output to the code will be the following:

"Super constructor"
"Method m() of class Sub"
NullPointerException

The NullPointerException occurs because p.getName() cannot be invoked since Person p = new Person() is instantiated with null. (This is on purpose!)

The program is executed in the following order:

  1. The main method in the ConstructorTest class is executed.
  2. new Sub() is called, which invokes the constructor of the Sub class.
  3. The Sub class constructor starts executing.
  4. Since the Sub class extends the Super class, the superclass constructor Super() is called before the body of the Sub class constructor.
  5. Inside the Super class constructor:
    a) The line System.out.println("Super constructor"); prints "Super constructor" to the console.
    b) The method m() is called. Since the Sub class overrides this method, the overridden version in the Sub class will be invoked. However, at this point, the Sub class constructor has not finished executing yet.
  6. The control goes back to the Super class constructor, and the constructor execution completes.
  7. The control returns to the Sub class constructor.
  8. The line Person p = new Person(); is encountered.
  9. The Person class constructor is invoked, which doesn't have any explicit output.
  10. The instance of the Person class is created and assigned to the p instance variable.
  11. The control goes back to the Sub class constructor, and the constructor execution completes.
  12. The line System.out.println("Sub constructor"); in the Sub class constructor is executed, printing "Sub constructor" to the console.
  13. The m() method is called again, this time within the Sub class constructor.
  14. Inside the overridden m() method in the Sub class:
    a) The line System.out.println("Method m() of class Sub"); is executed, printing "Method m() of class Sub" to the console.
    b) The line System.out.println(p.getName()); is executed, which calls the getName() method on the initialized p instance of the Person class and prints "John Doe" to the console.

Now to my question:

When invoking the Super class constructor, Java first prints System.out.println("Super constructor"); to the console. In the next step, it would call Super.m(). However, since Super.m() gets overridden in the Sub class, it calls Sub.m() instead. How does Java know that the Sub class overrides the method m() from the Super class if the order of instantiation dictates that the super class constructor has to be invoked BEFORE the sub class constructor? Does it recursively check each base class method for a subclass method of the same name? Is the Sub class "scanned" prior to instantiating the base class, and thus Java knows how to handle a base class method when it encounters it upon instantiation?

Basically, I would like to get a better understanding of what happens behind the scenes, when Java overrides a class method.

like image 614
MounkeySoup318 Avatar asked Oct 31 '25 13:10

MounkeySoup318


1 Answers

You are assuming that the initialization of the class metadata depends on the initialization of the object, but this is not the case. Classes are initialized before the first instance of this class is created and all instances share the class data.

E.g. when you run the following program

public class Super {
    public void method1() {}
    public void method2() {}
}
public class Sub extends Super {
    @Override public void method2(){}

    public static void main(String[] args) throws NoSuchMethodException {
        System.out.println(Sub.class.getMethod("method1").getDeclaringClass());
        System.out.println(Sub.class.getMethod("method2").getDeclaringClass());
    }
}

It will print

class Super
class Sub

demonstrating that it is known which method has been overridden, even if no instance of this class exists. (The classes and methods have to be public for this example to work)

online demonstration (uses inner classes to put two public classes in one file)

We can also demonstrate that each object is invariably an instance of its class, regardless of the execution of the constructors:

public class Sub extends Super {
    public static void main(String[] args) throws NoSuchMethodException {
        new Sub();
    }
}
class Super {
    Super() {
        System.out.println("Constructing object of type " + getClass());
    }
}

prints

Constructing object of type class Sub

When you execute new Sub(), two things happen:

  • First, memory is allocated for an instance of Sub, large enough to hold all instance fields belong to Sub (including inherited fields). The object’s meta data is initialized to indicate that it is an instance of Sub (think of an unchangeable pointer to the class metadata) and all fields initialized to the default value for its type (zero, false, or null).

  • Then, the constructor is executed. This includes the execution of the field initializer and initializer blocks, which happens after the super constructor call but before any other statement of the constructor.
    The super constructor may invoke overridden methods, as discussed, but it’s usually not a good idea to invoke overridable methods in a constructor, as these overridden methods will see the fields of the the subclass in their default state.

public class Sub extends Super {
    public static void main(String[] args) throws NoSuchMethodException {
        new Sub();
    }

    int value = 10;

    @Override
    void method() {
        System.out.println("value = " + value);
    } 
}
class Super {
    Super() {
        System.out.println("Constructing object of type " + getClass());
        method();
    }
    void method() {}
}

prints

Constructing object of type class Sub
value = 0

online demonstration

like image 81
Holger Avatar answered Nov 02 '25 04:11

Holger



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!