The labs on this site need JavaScript to run. The same content is written out below.
Learn › Java › Classes and objects
Inheritance and overriding
When does the superclass constructor run when a subclass object is created?
A single object of a subclass contains both the fields inherited from the superclass and the fields that the subclass declares. When you use new, the superclass constructor finishes first, and only then do the field initializers and the constructor body of the subclass run. Because of this order, there are cases in which you must write super(...), and when the superclass constructor calls an overridden method, that method sees subclass fields that are still empty.
Checked against real execution · All 36 combinations were compiled and run on JDK 21, and the compilation result and the output were checked against the results
Concepts in this lab
- Inheritance
- This means defining a new class (the subclass) that receives the fields and methods of an existing class (the superclass). It reuses common code, and the subclass adds or overrides only the parts it needs. A Java class supports single inheritance only, and a change to the superclass affects every subclass, so it is best used only when there is a clear "is-a" relationship.
- Overriding
- This means that a subclass redefines a method of its superclass (or of an interface) with the same signature. At run time, the method that matches the type of the actual object is called, and this is how polymorphism is implemented. The access level cannot be made narrower, and static, private, and final methods cannot be overridden.
- Constructor
- This is code that runs once when an object is created with new and puts the fields into their initial state. Its name is the same as the class name, and it has no return type. A class can have several of them as long as they take different arguments, and the one that matches the arguments passed to new is selected. Its body runs after the fields have been set to their default values and the field initializers and initializer blocks have run.
- super
- This keyword refers, inside a subclass, to things that belong to the superclass. Writing super(arguments) as the first statement of a constructor calls the superclass constructor that matches those arguments, and if you do not write it, the compiler automatically inserts super() with no arguments. So when the superclass has no no-argument constructor, you must write super(arguments) yourself in the subclass constructor, and not writing it is a compile-time error. Writing super.method() inside a method calls the overridden method of the superclass.
- Default values of fields
- These are the values that are automatically placed in the fields that are not given a value when an object is created. Numeric types such as int get 0, boolean gets false, and reference types such as String get null. These values are what you see until a field initializer or a constructor assigns a value. Local variables inside a method have no default value, so reading one without assigning a value to it is a compile-time error.
Try these
- Default settings — The superclass constructor calls describe(). sound is assigned "Woof", yet sound=null is printed.
- Only the order of execution — The order is the superclass constructor, the field initializer of the subclass, and then the body of the subclass constructor.
- Without super — The compiler inserts super(), which calls Animal() of the superclass.
- Without super when the superclass has no Animal() — The automatically inserted super() has no constructor to call, so this is a compile-time error.
- Calling the superclass method too, with super.describe() — Inside the overriding method, the original behavior of the superclass runs first.
- When the superclass has two constructors — The superclass constructor that matches the arguments passed to super is selected.
Running with the default settings
- Compilation: The compiler looks at the first line of the constructor Dog(). super("Buddy") means that the superclass constructor Animal(String name), which takes one string, will be called. Animal has this constructor, so the code compiles.
- new Dog() runs: Memory for one Dog object is allocated on the heap. This object contains both name, inherited from Animal, and sound, declared by Dog, and both are set to their default value, null.
- The first line of the constructor Dog(), super("Buddy"): Before the field initializer and the constructor body of Dog run, control first passes to the superclass constructor, Animal(String name). This is because the inherited part must be ready first for the subclass to be able to use it.
- this.name = name in the body of Animal(String name): The "Buddy" that was passed in is assigned to name. sound is still null.
- System.out.println("Animal(String) runs"): The body of the superclass constructor is running. Not a single line of the body of the Dog() constructor has run yet.
- Animal(String name) calls describe() in its body: Which describe() runs is determined by the type of the actual object that is being created now. The actual object is a Dog, so the describe() that Dog overrides runs, even though the call is made inside the superclass constructor.
- println("sound=" + sound) inside describe() in Dog: The field initializer of Dog, sound = "Woof", runs only after the superclass constructor finishes. Right now sound has its default value, null, so the output is sound=null.
- The field initializer of Dog, String sound = "Woof": The superclass constructor has finished, and control is back in Dog(). Only now does the field initializer of Dog run, and sound is set to "Woof".
- System.out.println("Dog() runs"): The body of the Dog() constructor runs. The body of the subclass constructor comes last of all.
- Dog d = new Dog(): All the constructors have finished. The reference to the completed object is stored in the variable d.
- Called through d.describe(), println("sound=" + sound) inside describe() in Dog: The object has been fully created by now, so sound holds "Woof". The output is sound=Woof.
Result of this run
- The order of execution is: the superclass constructor Animal(String name) → the field initializer of Dog → the body of the Dog() constructor. The subclass constructor starts first, but on its first line it passes control to the superclass constructor, so the superclass side finishes first.
- Pitfall: The describe() that the superclass constructor called is the one that Dog overrides, and at that point sound in Dog is not initialized yet, so it is null. Even though the code says sound = "Woof", sound=null was printed. If a method had been called on the field that is null, a NullPointerException would have been thrown.
- For this reason, the rule is that a constructor does not call methods that a subclass can override. The output of d.describe(), called after the object was fully created, is sound=Woof, as it should be.
- Dog overrides describe(), so describe() of Animal does not run. name was not printed. To run the superclass method as well, call super.describe() inside describe() of Dog.
Code
class Animal {
String name;
Animal(String name) {
this.name = name;
System.out.println("Animal(String) runs");
describe(); // calls an overridden method from the constructor
}
void describe() {
System.out.println("name=" + name);
}
}
class Dog extends Animal {
String sound = "Woof";
Dog() {
super("Buddy"); // calls Animal(String) of the superclass
System.out.println("Dog() runs");
}
@Override
void describe() {
System.out.println("sound=" + sound);
}
}
Dog d = new Dog();
d.describe();
Assumptions of this model
All 36 combinations were actually compiled and run with the same code on JDK 21 on this PC, and the compilation result, the error message, the output while the object is being created, and the output of d.describe() were checked against the results. It was also confirmed by compiling that the same error occurs when the subclass declares no constructor and the superclass has no no-argument constructor, and that super(...) must be the first statement of a constructor (as of JDK 21). In newer JDKs this is said to have changed so that some statements can come before super(...), but this was not verified on this PC. Simplifications: the constructor of Animal also calls the constructor of its own superclass, Object, before anything else, and this step is left out. The detailed rules for which method is selected are covered in the "Polymorphism and method selection" lab. The error message is the first line of the English message from javac.
Previous lab: static and instance · Next lab: Polymorphism and method selection