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:
ConstructorTest class is executed.new Sub() is called, which invokes the constructor of the Sub class.Sub class constructor starts executing.Super class, the superclass constructor Super() is called before the body of the Sub class constructor.Super class constructor: System.out.println("Super constructor"); prints "Super constructor" to the console. 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.Super class constructor, and the constructor execution completes.Sub class constructor.Person p = new Person(); is encountered.Person class constructor is invoked, which doesn't have any explicit output.Person class is created and assigned to the p instance variable.Sub class constructor, and the constructor execution completes.System.out.println("Sub constructor"); in the Sub class constructor is executed, printing "Sub constructor" to the console.m() method is called again, this time within the Sub class constructor.m() method in the Sub class: System.out.println("Method m() of class Sub"); is executed, printing "Method m() of class Sub" to the console. 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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With