Think about how we describe living things. A dog and a cat are obviously different — one barks, one meows — but they also share a huge amount: both are animals, both have a name, both eat, both sleep, both can be described. It would be silly to write out "has a name", "eats", "sleeps" separately for every kind of animal. We'd rather say it once, for animals in general, and then let each specific animal add only what makes it special.
Inheritance is exactly this idea in code. You write a general
superclass (also called the parent or base class), and then a
more specific subclass (the child or derived class)
extends it. The subclass automatically inherits all the fields and
methods of its parent — the shared behaviour is written just once — and is then free to
add new members of its own and to override inherited ones to
behave differently.
Inheritance models an "is-a" relationship: a Dog is-an Animal, a
Cat is-an Animal. If you can honestly say "a B is an A", then making
B extends A is a good fit — and that little test will save you from the most common
design mistake, which we'll meet at the end.
We draw inheritance as a tree. The general class sits at the top; each arrow runs
from a subclass up to its superclass, meaning "is-a". Everything an
Animal can do, a Dog and a Cat can do too — because they
are animals — plus their own extras.
Notice the shared members — name, eat(), describe() — live
only at the top. They are not copied down; they are inherited. Change
describe() once in Animal and every subclass instantly gets the new
behaviour. This is the whole payoff: write shared behaviour once.
Here is Animal, a base class with a name field and two methods. Then
Dog and Cat each extends Animal. They don't repeat
name or describe() — they get those for free. What they do is
override speak() so each makes its own sound. Press Run.
Look closely at describe(). It lives only in Animal, yet when
rex.describe() runs, the call to this.speak() uses the Dog's
version. The object always runs its own most specific method — a Dog barks even
though describe() was written for a generic animal. That's the power of overriding.
super
A subclass isn't limited to the parent's members — it can add its own. A
Dog can fetch(); an Animal in general cannot. And when a
subclass needs its own constructor (say, to store an extra field like breed), it must
first call the parent's constructor with super(...) so the inherited part of the
object is set up. You can also call super.method() to reuse the parent's version of a
method and then add to it. Run this:
Three things are happening here: super(name) initialises the inherited
name; fetch() is a new member on Dog only;
and super.describe() lets Dog's describe() build on
the parent's rather than replacing it entirely.
If a variable's type is Animal, the compiler only lets you call things every
animal has — so animal.fetch() is a type error, because not every animal
can fetch. The object at runtime really is a Dog and does have
fetch(), but from the wider Animal viewpoint that extra ability is
invisible until you narrow the type back down (e.g. with a check like
if (a instanceof Dog)). A subclass can always be used where the parent is
expected; the reverse is not guaranteed.
Because every subclass is-an Animal, we can put a Dog, a
Cat and anything else that extends Animal into a single
Animal[] and process them uniformly. We call describe() on each without
caring which specific kind it is — yet each still speaks with its own voice. This is the pattern
that makes big programs manageable:
Adding a new animal later — a Sheep that says "Baa!" — needs no change
to this loop at all. Write the subclass, drop it in the list, done. Shared code stays shared;
differences stay local to each subclass.
The single most common inheritance mistake is using it for a "has-a"
relationship. Ask the little question before you write extends:
Car is-a Vehicle ✓ → inheritance
(class Car extends Vehicle).Car has-an Engine ✗ → not
inheritance. A car is not a kind of engine! An engine is a part the car
contains — so it belongs as a field.Storing a part as a field like this is called composition. The reliable test: if you can say "a B is a kind of A", use inheritance; if you'd say "an A has a B" or "is made of a B", use a field instead.
Reaching for extends whenever two classes share a little code — rather than only
when there's a genuine "is-a" — leads to tangled, fragile hierarchies. When in doubt, prefer
composition: it's more flexible and easier to change.