Inheritance
The "is-a" relationship — extending classes, calling `super`, and the trade-offs of building tall class trees
Inheritance is the relationship that says one class is a kind
of another. A Dog is a Mammal. A SavingsAccount is a
BankAccount. A Square is a Rectangle — well, maybe — we'll
come back to that.
Inheritance is the most famous OOP feature and also the most
overused. This page will teach you how it works in Java and when it
is the right tool — not just how to type extends.
A first family
The hollow arrow <|-- in UML means inheritance — Dog
is-an Animal. In Java, the keyword is extends.
Three new pieces of Java vocabulary appear:
| Word | What it does |
|---|---|
extends | Declares "this class inherits from..." |
super(...) | Inside a subclass constructor: calls the parent constructor |
@Override | Annotation that says "I'm replacing an inherited method" (the compiler checks this) |
A subclass automatically inherits all public and protected
members from its parent. It can add new fields and methods, and it
can override inherited methods to change their behavior.
What super actually does
When you build a Dog, you build an Animal first, then add the
Dog-specific bits on top. In memory, every Dog instance includes
the state of an Animal.
super(name) in the Dog constructor is what runs the Animal
constructor on the Dog-being-built to set up name. It must be
the first statement in the subclass constructor.
If you omit super(...), Java silently inserts super() — a call to
the parent's no-arg constructor. If the parent doesn't have a
no-arg constructor, that silent insertion fails and you must call
the right one explicitly.
Overriding: replacing inherited behavior
When a subclass declares a method with the same signature as one inherited from the parent, the subclass version replaces the parent's version for instances of the subclass.
The @Override annotation isn't strictly required, but it's free
help: if you misspell the method name or get the parameter types
wrong, the compiler refuses to compile. Always write it.
Calling the parent's version from the override
Sometimes the subclass wants to add to the parent's behavior
rather than completely replace it. You can call the parent's version
explicitly with super.methodName(...).
Manager.monthlyPay() reuses everything Employee knows about pay
and adds a bonus on top. If the salary formula in Employee changes
(say, to add overtime), Manager automatically benefits.
final classes and methods: closing the door
You can opt out of being extended:
final class Foo { ... }— no class mayextends Foo.finalon a method — subclasses may not override it.
final is a useful design tool. Java's String is final — for
good reason; if anyone could subclass String and break its rules,
the entire JVM's security model would collapse.
The trap: inheritance is strong coupling
The reason the GoF say "favor composition over inheritance" is not that inheritance is bad. It's that inheritance is very strong. A subclass depends on its parent's internal design. If the parent changes how it stores its data or which methods it calls internally, subclasses can break in surprising ways. This is sometimes called the fragile base class problem.
Use inheritance when you genuinely have an is-a relationship and the parent class was designed for extension. For everything else, reach for composition first.
A famous cautionary example: a Square is a Rectangle
mathematically, but if you make Square extends Rectangle, you
quickly discover that setting a square's width to a value different
from its height violates the square's invariant. The "is-a"
relationship in mathematics does not always translate cleanly into
behavioral substitutability in software.
Practice
Build a small Shape hierarchy:
Shapehas a methodarea()that returns0.0and a methoddescribe()that returns"Shape with area " + area().CircleextendsShapewith a private finaldouble radiusand overridesarea()to returnMath.PI * radius * radius.SquareextendsShapewith a private finaldouble sideand overridesarea()to returnside * side.
The provided Main builds a small list of shapes and prints each one. Expected output (the radius-1 area uses Java's Math.PI):
Shape with area 0.0
Shape with area 3.141592653589793
Shape with area 16.0
Test your understanding
What does super(name) do inside a subclass constructor?
Calls a static method on the parent class
Invokes the parent class's constructor with the given arguments to initialize the parent part of the new object
Creates an entirely separate parent object
Marks the constructor as overriding
Why is the @Override annotation a good habit?
It is required for the override to take effect at runtime
It tells the JVM to skip the parent method entirely
It asks the compiler to verify that you really are overriding an inherited method, catching typos and signature mismatches at compile time
It improves runtime performance of the method
Which is the best reason to choose inheritance over composition?
You want to reuse code from another class
The other class has many methods
The new class is genuinely a kind of the other class, and the parent was designed for extension
You want to avoid creating new files