JAVA / INHERITANCE AND POLYMORPHISM
Inheritance, extends and the is-a relationship
Use extends to make a subclass that inherits its parent's accessible members, and judge whether the is-a claim it makes is actually true.
What you will learn
- Declare one direct superclass with extends and inherit its accessible members
- Read extends as an is-a claim the subclass must honour everywhere the parent is used
- Reach private parent state through the parent's methods, or declare the field protected
- Recognise that a class with no extends clause silently extends Object
Understanding Inheritance, extends and the is-a relationship
The declaration `class Manager extends Employee` tells the compiler two separate things at once. Structurally, every Manager object is laid out as an Employee plus whatever Manager adds, so the parent's fields and methods are physically present in each instance and there is nothing to copy. Type-wise, the name Manager becomes a subtype of Employee, which is why `Employee e = m;` compiles with no cast while the opposite direction does not. Java gives each class exactly one direct superclass, so that parent slot is a scarce resource you spend once.
The useful way to read extends is as a promise to every caller holding an Employee reference: anything they can ask an Employee to do, they can ask this Manager to do, and the result still makes sense. That is the is-a test, and it is about behaviour, not about which class happens to contain code you would like to reuse. `java.util.Stack` extends `Vector`, which is exactly why you can call `get(0)` or `insertElementAt` on a stack: inheriting a parent hands callers its entire public surface, including the parts your new type should never expose.
Inheritance copies nothing and removes nothing from the object; it only decides which names your subclass body is allowed to write. A private field in the parent is allocated in every subclass instance, yet the subclass cannot mention it, because private limits access to the body of the declaring class — the subclass goes through the parent's methods, or the parent declares the field protected. Constructors are not inherited members either, so `new Manager()` works only when Employee offers a constructor the subclass can chain to.
// Main.java
class Employee {
protected String name;
protected int baseSalary;
void raise(int amount) {
baseSalary += amount;
}
void printPay() {
System.out.println(name + " earns " + baseSalary);
}
}
class Manager extends Employee {
int bonus; // new state, added by the subclass
int totalPay() {
return baseSalary + bonus; // baseSalary is inherited from Employee
}
}
public class Main {
public static void main(String[] args) {
Manager m = new Manager();
m.name = "Ada"; // field declared in Employee
m.baseSalary = 5000;
m.bonus = 1200; // field declared in Manager
m.raise(500); // method inherited from Employee
m.printPay();
System.out.println("total " + m.totalPay());
Employee e = m; // legal: a Manager is-a Employee
e.raise(100); // same object, reached through the parent type
e.printPay();
System.out.println("total " + m.totalPay());
}
}extends declares a permanent subtype relationship: a subclass instance carries the parent's members and can be used wherever the parent type is expected.
Worked examples
What private means for a subclass
Shows that a private parent field exists in every subclass instance but can only be reached through the parent's own methods.
// Main.java
class Timer {
private int ticks;
public void tick() {
ticks++;
}
public int getTicks() {
return ticks;
}
}
class LoggingTimer extends Timer {
// int peek() { return ticks; } // will not compile: ticks is private to Timer
void report() {
System.out.println("ticks so far: " + getTicks());
}
}
public class Main {
public static void main(String[] args) {
LoggingTimer t = new LoggingTimer();
t.tick();
t.tick();
t.tick();
t.report();
System.out.println("the object stores " + t.getTicks() + " ticks");
}
}Example explained
Line 1`private int ticks` is allocated inside every LoggingTimer object, because an object's layout is the whole chain Timer plus LoggingTimer.
Line 2The commented-out `peek()` marks the limit: private confines the name to Timer's body, so the subclass cannot write `ticks` even though the storage is there.
Line 3Inside `report()`, `getTicks()` is called with no receiver and resolves to the inherited public method, which is the subclass's legal route to that state.
Line 4Three `tick()` calls through the LoggingTimer reference change that one inherited field, so both printed numbers are 3.
Walking the superclass chain
Shows that each class has exactly one direct superclass and that the chain always terminates at Object.
// Main.java
class Appliance { }
class Fridge extends Appliance { }
class IceMaker extends Fridge { }
public class Main {
public static void main(String[] args) {
Class<?> c = IceMaker.class;
while (c != null) {
System.out.println(c.getSimpleName());
c = c.getSuperclass();
}
}
}Example explained
Line 1`getSuperclass()` returns a single class, not a list, because a class may name only one direct superclass — the walk is a straight line, never a tree.
Line 2`Appliance` has no extends clause, yet the chain continues to Object: the compiler supplies that parent for you.
Line 3`Object.getSuperclass()` returns null, which ends the loop and marks the root that every Java class hierarchy shares.
Line 4This chain is also the lookup order for an inherited member name such as `toString`: the search starts at IceMaker and stops at the first class that declares it.
Important notes
Constructors are not inherited. `new Manager()` compiles above because Employee has an implicit no-arg constructor; the moment Employee declares only `Employee(String name)`, Manager stops compiling until it passes an argument up.
The relationship is fixed at compile time and one class wide: there is no `extends A, B` for classes, and nothing can change an object's superclass while the program runs.
Common mistakes
Re-declaring an inherited field in the subclass, such as writing `protected int baseSalary;` again inside Manager: there are now two fields with that name, the parent's methods keep updating the parent's copy, and the subclass keeps reading its own 0.
Extending a class just to borrow one convenient method: the subclass also inherits the parent's entire public API, so callers can invoke operations the new type should forbid, and you cannot withdraw them later without breaking code.
Using a private parent field by name in the subclass and concluding it was not inherited: the compiler reports `baseSalary has private access in Employee`, even though the field really is part of every subclass instance.
Try it yourself
Change, predict, then run
In one file, write `class Book` with protected fields `title` and `pages` plus a `describe()` method that prints them, then `class Audiobook extends Book` that adds `int minutes` and a `listeningTime()` method printing the inherited `title` together with `minutes`. Create a single Audiobook, set all three fields, and call both methods.
Open the Java workspaceCheck your understanding
Printer declares `private int pageCount` and a public `printPage()` that increments it. LaserPrinter extends Printer. Which statement is true?
- LaserPrinter objects have no pageCount field until LaserPrinter declares one itself.
- Each LaserPrinter object contains pageCount, but LaserPrinter's own code cannot refer to it by name.
- LaserPrinter inherits pageCount and may read it directly, because a subclass bypasses private.
- pageCount becomes shared by all LaserPrinter instances because it was declared in the parent.
Show answer
An object's fields come from the entire inheritance chain, so every LaserPrinter instance carries pageCount; private governs only which code may write that name, and it limits that to Printer's body. The tempting answer, that a subclass can read private parent state directly, confuses inheritance with access: LaserPrinter must go through printPage() or another Printer method to reach the value.