How to learn Inheritance
Inheritance lets a subtype reuse and specialize a parent contract, but reuse alone is not enough reason to extend a class. The important test is substitutability: code written for the parent should continue to behave correctly when given the child.
Prefer a shallow hierarchy and keep shared behavior small. When objects merely collaborate or one contains another, composition is usually clearer. Use an interface when unrelated classes need to promise the same operation without inheriting implementation or state.
Recommended learning order
- Extend and construct: Learn accessible inherited members, constructor chaining, and super. Remember that parent construction completes before the child constructor body runs.
- Override behavior: Use @Override, preserve the parent contract, and distinguish runtime overriding from compile-time overloading.
- Program to a contract: Store subtype objects in interface or parent references and observe dynamic dispatch when an overridden method is called.
- Choose abstraction boundaries: Compare abstract classes, interfaces, sealed hierarchies, and composition based on shared state, permitted variants, and change cost.
Tested example: Dispatch through an interface
The loop depends only on the Shape contract. Each object supplies its own area implementation, so adding another shape does not require changing the loop.
public class Main {
interface Shape { double area(); }
record Rectangle(double width, double height) implements Shape {
public double area() { return width * height; }
}
record Circle(double radius) implements Shape {
public double area() { return Math.PI * radius * radius; }
}
public static void main(String[] args) {
Shape[] shapes = {new Rectangle(3, 4), new Circle(1)};
for (Shape shape : shapes) {
System.out.printf("%.2f%n", shape.area());
}
}
}Expected output
12.00
3.14Common mistakes
- Changing the parameter list and thinking a method was overridden; @Override catches this accidental overload.
- Downcasting a parent reference without checking its runtime type, which can throw ClassCastException.
- Calling overridable methods from constructors before the child fields have been initialized.
- Creating a deep hierarchy for code reuse when a contained helper object would make dependencies explicit.
How to practice
Work from basic extension to overriding, then abstract classes and interfaces. For each hierarchy, write a sentence explaining why every child can safely stand in for its parent. If that sentence needs exceptions, reconsider the design.
Exercises to do first
- Basic inheritance — Identify which members are inherited and which remain inaccessible.
- Method override — Use @Override and observe runtime dispatch through a parent reference.
- Shape hierarchy — Model one stable contract with different implementations.