Inheritance: extends, super and the constructor chain
What a subclass gets from its superclass, in what order constructors run, and the difference between overriding and overloading. A favorite of output questions.
Inheritance is building a new class on top of an existing one. The new class (the subclass) takes the fields and methods of the old one (the superclass), and adds its own things or changes some methods. In the exam this topic comes in two forms most often: output questions asking in what order the constructors print, and compile errors involving super.
Golden rules:
- Every constructor first calls the superclass constructor. If you do not write it, the compiler adds
super();. - Output order is top down: first Object, then the superclass, and the subclass last.
- A private field is inherited but cannot be accessed by name. The subclass uses a getter or the field becomes
protected. - Override: same signature, runs according to the actual type of the object. Overload: different parameters, chosen at compile time.
- Fields are not overridden. Only methods are.
extends and the “is-a” relationship
When you write class Manager extends Employee, you are saying “every Manager is an Employee” (is-a relationship). Manager uses all of Employee’s public and protected members as if they were its own. In Java a class can extend only one class (single inheritance). If you want several types of behavior, you use an interface (week 4).
You tell when to use inheritance with the “is-a” test. “A Manager is an Employee” is true, so inheritance fits. “A Car is an Engine” is false; a car has an engine (has-a). In that case you put an Engine field inside Car (composition, week 7).
class Employee {
private String name;
protected double salary; // subclasses can see it directly
public Employee(String name, double salary) {
this.name = name;
this.salary = salary;
System.out.println("Employee ctor: " + name);
}
public String getName() { return name; }
public double pay() { return salary; }
@Override
public String toString() { return "Employee " + name + " " + pay(); }
}
class Manager extends Employee {
private double bonus; // specific to Manager
public Manager(String name, double salary, double bonus) {
super(name, salary); // the superclass constructor
this.bonus = bonus;
System.out.println("Manager ctor: bonus " + bonus);
}
@Override
public double pay() { return super.pay() + bonus; }
@Override
public String toString() { return "Manager " + getName() + " " + pay(); }
}
Manager m = new Manager("Selin", 5000, 1500);
System.out.println(m);
System.out.println(m.pay());
Employee ctor: Selin
Manager ctor: bonus 1500.0
Manager Selin 6500.0
6500.0
In Manager’s toString we wrote getName(), not name. name is private in Employee; if you had written name inside Manager, you would get the compile error name has private access in Employee.
Constructor chaining
Constructors are not inherited. But when a subclass object is created, the superclass part has to be built too. That is why every constructor calls a superclass constructor on its first line:
- If you write
super(arguments), that constructor is called. - If you write nothing, the compiler secretly adds
super();(no arguments). - If the superclass has no no-argument constructor, this hidden call gives a compile error.
class A { A() { System.out.println("A"); } }
class B extends A { B() { System.out.println("B"); } } // hidden super()
class C extends B { C() { super(); System.out.println("C"); } }
new C();
A
B
C
new C()
C() called → first job super() → B()
B() called → first job super() → A()
A() called → super() → Object() (silent)
A body: "A"
B body: "B"
C body: "C"
PITFALL ·
this(...)andsuper(...)together. A constructor starts with eitherthis(...)orsuper(...), never both. A constructor that starts withthis(...)does not call the superclass itself; the constructor at the end of the chain does. The Truck example below shows this.
class Vehicle {
int wheels;
Vehicle(int w) { wheels = w; System.out.println("Vehicle " + w); }
Vehicle() { this(4); System.out.println("Vehicle()"); }
}
class Bike extends Vehicle { Bike() { super(2); System.out.println("Bike"); } }
class Truck extends Vehicle { Truck() { System.out.println("Truck"); } }
Bike b = new Bike();
Truck t = new Truck();
System.out.println(b.wheels + " " + t.wheels);
Vehicle 2
Bike
Vehicle 4
Vehicle()
Truck
2 4
When are field initializers assigned?
When an object is created, the order is: (1) all fields get default values (0, null, false), (2) super(...) runs, (3) this class’s field initializers (int x = 7;) are assigned, (4) the rest of the constructor body runs. If a superclass constructor calls an overridden method, that method sees the subclass’s fields still at their default values:
class Base {
Base() { System.out.println("Base ctor"); hello(); }
void hello() { System.out.println("Base hello"); }
}
class Derived extends Base {
int x = 7;
Derived() { System.out.println("Derived ctor x=" + x); }
@Override void hello() { System.out.println("Derived hello x=" + x); }
}
new Derived();
Base ctor
Derived hello x=0
Derived ctor x=7
PITFALL · the
super(...)first-line rule. The textbook and the exam follow the rule that thesuper(...)call must be the first line of the constructor; that is the case in Java 24 and earlier. The flexible constructor bodies feature in Java 25 allowed lines before it that do not touchthis(if you compile with--release 21you get the errorflexible constructors is not supported). If you see an option in the exam like “super is not on the first line”, answer by the old rule: compile error.
protected and access
| Member in the superclass | Access from inside the subclass |
|---|---|
public | yes |
protected | yes (even if the subclass is in another package) |
| no modifier (package-private) | only if in the same package |
private | no (it exists in the object, cannot be accessed by name) |
protected is convenient but loosens encapsulation a bit. In most cases keeping the field private and providing a getter is better; in the exam both solutions are accepted.
Overriding and overloading
The two concepts are confused because of their names, but they are completely different mechanisms:
| Overriding | Overloading | |
|---|---|---|
| Where? | In the subclass, you rewrite a method of the superclass | In the same class (or with an inherited method), same name |
| Parameter list | Same | Different (number, type or order) |
| Return type | Same or a subtype | Does not matter, it does not distinguish on its own |
| Access | Cannot narrow (public → public) | Free |
| When is it chosen? | At run time, by the actual type of the object | At compile time, by the argument types |
@Override | Written | Not written |
class Printer {
void show(int a) { System.out.println("int " + a); }
void show(double a) { System.out.println("double " + a); }
void show(String s) { System.out.println("String " + s); }
}
Printer pr = new Printer();
pr.show(3);
pr.show(3.0);
pr.show('A'); // char widens to int
pr.show(5L); // long widens to double
pr.show("3");
int 3
double 3.0
int 65
double 5.0
String 3
Inside an overridden method you can call the superclass version with super.method(). This is the standard way to add on top without rewriting the code:
class Shape { String name() { return "shape"; } }
class Circle extends Shape {
@Override String name() { return "circle<" + super.name() + ">"; }
}
System.out.println(new Circle().name());
circle<shape>
Field hiding
If a subclass declares a field with the same name as the superclass, there are two separate fields. Which field is read is decided by the type of the variable, not of the object. For methods, the actual type of the object decides.
class P { String tag = "P"; String getTag() { return tag; } }
class Q extends P { String tag = "Q"; @Override String getTag() { return tag; } }
P obj = new Q();
System.out.println(obj.tag + " " + obj.getTag());
P Q
The Object class
Every class for which you do not write extends automatically derives from java.lang.Object. So every object has these methods:
| Method | Default behavior | What is usually done? |
|---|---|---|
toString() | ClassName@hash | Override for readable text |
equals(Object o) | this == o | Override for content comparison |
hashCode() | A number specific to the object | Overridden too if equals is overridden |
getClass() | The run-time class | Used like getClass().getSimpleName() |
Book b1 = new Book("Dune"), b2 = new Book("Dune"); // equals NOT overridden
System.out.println(b1.equals(b2));
System.out.println(b1.getClass().getSimpleName());
System.out.println(b1 instanceof Object);
false
Book
true
final
final class: nobody can derive from it (StringandIntegerare like this).finalmethod: a subclass cannot override it, but overloading is free.finalfield: assigned once, then it does not change.
Common mistakes
| Wrong idea | The truth |
|---|---|
| Constructors are inherited | They are not; every class writes its own constructor and calls the one above with super(...) |
| If I do not write super, the superclass is not built | The compiler adds a hidden super(); if the superclass has no no-argument constructor, it is an error |
| Output starts from the subclass | Bodies run top down: A, B, C |
| A subclass can use a private field | It cannot access it by name; getter or protected |
| Same name, different parameters = override | That is an overload; for an override the parameters must be the same |
| I can drop public when overriding | Access cannot be narrowed |
| Fields are overridden too | They are hidden; ref.field looks at the type of the variable |
| The method called by the superclass constructor is the superclass’s | If it is overridden, the subclass’s runs, with its fields still at defaults |
super.super.method() | There is no such thing, only one level up |
Test yourself
Exam-style questions. Solve them on paper first, then reveal the answer.
What is printed by new C();?
class A { A() { System.out.println("A"); } }
class B extends A { B() { System.out.println("B"); } }
class C extends B { C() { super(); System.out.println("C"); } }Show answer
The first job of every constructor is to call the superclass constructor. Even if super() is not written in B, the compiler adds it. The calls go down C → B → A, but the bodies run starting from the top: A prints first, then B, and C last.
What is printed?
class Employee {
private String name; protected double salary;
Employee(String name, double salary) {
this.name = name; this.salary = salary;
System.out.println("Employee ctor: " + name);
}
String getName() { return name; }
double pay() { return salary; }
}
class Manager extends Employee {
private double bonus;
Manager(String n, double s, double b) {
super(n, s); bonus = b;
System.out.println("Manager ctor: bonus " + bonus);
}
@Override double pay() { return super.pay() + bonus; }
}
// in main:
Manager m = new Manager("Selin", 5000, 1500);
System.out.println(m.pay());Show answer
super(n, s) first runs the Employee constructor: Employee ctor: Selin. Then the Manager body: since bonus is a double, 1500.0. pay() is overridden: super.pay() returns 5000.0, and 1500 is added on top: 6500.0.
Find the compile error.
class Vehicle {
int wheels;
Vehicle(int w) { wheels = w; }
}
class Car extends Vehicle {
Car() { System.out.println("Car"); }
}Show answer
If you do not write super(...), the compiler adds super();. Because Vehicle only defines Vehicle(int), there is no no-argument constructor. javac: constructor Vehicle in class Vehicle cannot be applied to given types; required: int. Fix: write super(4); as the first line of Car.
Find the compile error.
class Employee {
private double salary = 100;
}
class Manager extends Employee {
double pay() { return salary * 2; }
}Show answer
A subclass inherits a private field (it exists inside the object) but cannot access it by name. javac: salary has private access in Employee. Fix: make the field protected, or add getSalary() to Employee and call that.
What is printed?
class Printer {
void show(int a) { System.out.println("int " + a); }
void show(double a) { System.out.println("double " + a); }
void show(String s) { System.out.println("String " + s); }
}
// in main:
Printer p = new Printer();
p.show('A');
p.show(5L);Show answer
In overloading, the compiler chooses which method to call by looking at the argument type. 'A' is a char and there is no exact match; the closest type a char can widen to is int: int 65 (the code of A). 5L is a long; a long cannot narrow to int, it widens to double: double 5.0.
What is printed?
class Animal {
String sound() { return "..."; }
String describe() { return "I say " + sound(); }
}
class Dog extends Animal {
@Override String sound() { return "Woof"; }
}
// in main:
Animal d = new Dog();
System.out.println(d.describe());Show answer
describe() is written in Animal, but the sound() call inside it runs the version of the current object, that is, Dog's. An overridden method is always chosen from the actual class of the object (dynamic binding in week 3).
Find the compile error.
class Animal {
public String sound() { return "..."; }
}
class Cat extends Animal {
String sound() { return "Meow"; }
}Show answer
When overriding, the access modifier can stay the same or widen, but it cannot narrow. The method that is public in Animal is written without a modifier (package-private) in Cat. javac: attempting to assign weaker access privileges; was public. Fix: write public in Cat too.
What is printed?
class Vehicle {
int wheels;
Vehicle(int w) { wheels = w; System.out.println("Vehicle " + w); }
Vehicle() { this(4); System.out.println("Vehicle()"); }
}
class Bike extends Vehicle { Bike() { super(2); System.out.println("Bike"); } }
class Truck extends Vehicle { Truck() { System.out.println("Truck"); } }
// in main:
Bike b = new Bike();
Truck t = new Truck();
System.out.println(b.wheels + " " + t.wheels);Show answer
Bike explicitly calls super(2): Vehicle 2, then Bike. Truck has no super written, so the hidden super() calls the no-argument Vehicle, which delegates to Vehicle(int) with this(4): first Vehicle 4, then Vehicle(), and last Truck. The wheels are 2 and 4.
What is printed?
class Base {
Base() { System.out.println("Base ctor"); hello(); }
void hello() { System.out.println("Base hello"); }
}
class Derived extends Base {
int x = 7;
Derived() { System.out.println("Derived ctor x=" + x); }
@Override void hello() { System.out.println("Derived hello x=" + x); }
}
// in main:
new Derived();Show answer
Because the object is a Derived, even the hello() call inside Base's constructor runs Derived's version. But at that moment Derived's field initializers (x = 7) have not been executed yet; that assignment happens after super() finishes. x is printed with its default value 0. Then x becomes 7 and Derived's body runs.
What is printed?
class P { String tag = "P"; String getTag() { return tag; } }
class Q extends P { String tag = "Q"; @Override String getTag() { return tag; } }
// in main:
P obj = new Q();
System.out.println(obj.tag + " " + obj.getTag());Show answer
Fields are not overridden, they are hidden (field hiding). obj.tag looks at the compile-time type (P): "P". Methods, however, are overridden: getTag() runs Q's version and returns Q's tag: "Q". That is why you should not declare a field in a subclass with the same name as one in the superclass.
Which statement about final is true? (a) A final method cannot be overloaded. (b) A final class cannot be extended. (c) A final field can be changed in a subclass.
- (a)
- (b)
- (c)
Show answer
A final class cannot be inherited: for class Key extends Locked, javac says cannot inherit from final Locked (String is also a final class). A final method cannot be overridden but it can be overloaded; (a) is wrong. A final field is assigned once and nobody can change it; (c) is wrong.
Write a class Student that extends Person (which has a private name, a constructor Person(String name), getName() and toString() returning Person: <name>). Student adds a private double gpa, a constructor Student(String name, double gpa), and overrides toString() to return Person: Deniz, GPA 3.4 style text.
Show answer
name is private in Person, so Student can only fill it with super(name). In toString, instead of rewriting the superclass text, call super.toString():
public class Student extends Person {
private double gpa; public Student(String name, double gpa) {
super(name); // first line
this.gpa = gpa;
}
public double getGpa() { return gpa; } @Override
public String toString() {
return super.toString() + ", GPA " + gpa;
}
}Printing new Student("Deniz", 3.4) gives: Person: Deniz, GPA 3.4.Verification: All code examples and answers were run with JDK 26 (java verify/SEN1002/W02.java); the compile errors were tried with javac (verify/SEN1002/err/E02a-e.java, E02d also with --release 21).