Java Inheritance
Inheritance lets one class acquire the fields and methods of another. It models the “is-a” relationship: a Square is a Rectangle, a Car is a Vehicle. Java supports single class inheritance — a class can extend exactly one other class — and unlimited interface implementation.
This tutorial covers the syntax, the super keyword, method overriding, the @Override annotation, constructor chaining, and the modern sealed and permits keywords that let you control who can extend what.
1. The extends Keyword
class=class="tok-str">"tok-cmt">// parent (superclass)
public class Animal {
private String name;
public Animal(String name) { this.name = name; }
public String getName() { return name; }
public String sound() { return "?"; }
}
class=class="tok-str">"tok-cmt">// child (subclass)
public class Dog extends Animal {
public Dog(String name) {
super(name); class=class="tok-str">"tok-cmt">// call parent constructor
}
@Override
public String sound() {
return "Woof";
}
}
class=class="tok-str">"tok-cmt">// usage
Dog rex = new Dog("Rex");
System.out.println(rex.getName()); class=class="tok-str">"tok-cmt">// Rex - inherited
System.out.println(rex.sound()); class=class="tok-str">"tok-cmt">// Woof - overridden
The subclass automatically gets every non-private field and method from its parent. It can add new ones and override inherited ones. The single-inheritance rule keeps the language sane: there is no dreaded diamond problem from C++.
2. Method Overriding
A subclass can provide its own implementation of a method inherited from a parent:
public class Account {
private double balance;
public Account(double opening) { this.balance = opening; }
public double getBalance() { return balance; }
public void withdraw(double amount) {
if (amount > balance)
throw new IllegalArgumentException("insufficient funds");
balance -= amount;
}
}
public class SavingsAccount extends Account {
public SavingsAccount(double opening) { super(opening); }
@Override
public void withdraw(double amount) {
class=class="tok-str">"tok-cmt">// Savings accounts have a $class="tok-num">1 fee per withdrawal
super.withdraw(amount + class="tok-num">1.0); class=class="tok-str">"tok-cmt">// call parent's withdraw
}
}
@OverrideIf the method does not actually override a parent (typo in name, wrong parameter type), the compiler will tell you. This is one of the cheapest bug-catching tools in the language — use it religiously.
3. The super Keyword
super refers to the parent class. Use it to call the parent's constructor, to call the parent's overridden method, or to access a parent's hidden field:
public class Vehicle {
protected int speed;
public Vehicle(int speed) { this.speed = speed; }
public String describe() { return "moving at " + speed + " mph"; }
}
public class Car extends Vehicle {
private int wheels = class="tok-num">4;
public Car(int speed, int wheels) {
super(speed); class=class="tok-str">"tok-cmt">// must be first statement
this.wheels = wheels;
}
@Override
public String describe() {
class=class="tok-str">"tok-cmt">// call the parent's version, then extend
return super.describe() + " on " + wheels + " wheels";
}
}
Car c = new Car(class="tok-num">60, class="tok-num">4);
System.out.println(c.describe()); class=class="tok-str">"tok-cmt">// "moving at class="tok-num">60 mph on class="tok-num">4 wheels"
The first statement in a subclass constructor must be either this(...) (chain to another constructor in the same class) or super(...) (chain to a parent constructor). If you write neither, the compiler inserts an implicit super() call to the parent's no-arg constructor — which is an error if the parent has no no-arg constructor.
4. Methods Inherited from Object
Every class extends Object either directly or indirectly. Object defines toString, equals, hashCode, getClass, clone, finalize, and the thread-related wait / notify / notifyAll. Overriding toString, equals, and hashCode is common and important:
public class Person {
private final String name;
private final int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return "Person[" + name + ", " + age + "]";
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Person p)) return false;
return age == p.age && java.util.Objects.equals(name, p.name);
}
@Override
public int hashCode() { return java.util.Objects.hash(name, age); }
}
The contract between equals and hashCode must be honoured: equal objects must have equal hashcodes.
5. final Classes and Methods
Mark a class final to forbid extension; mark a method final to forbid overriding:
public final class Money { class=class="tok-str">"tok-cmt">// cannot be subclassed
private final long cents;
public Money(long cents) { this.cents = cents; }
public Money plus(Money other) { return new Money(this.cents + other.cents); }
}
public class Base {
public void templateMethod() { class=class="tok-str">"tok-cmt">/* ... */ }
public final void cannotOverride() { class=class="tok-str">"tok-cmt">/* sensitive code */ }
}
class=class="tok-str">"tok-cmt">// public class Ext extends Money {} // compile error
class=class="tok-str">"tok-cmt">// public class Sub extends Base { public void cannotOverride() {} } // compile error
Use final on classes that are not designed for inheritance — which is most of them. Effective Java recommends “design for inheritance or prohibit it”.
6. Sealed Classes (Java 17+)
A sealed class explicitly lists which classes may extend it. This gives you a closed inheritance hierarchy — useful for domain modelling and for exhaustive switch expressions:
public sealed interface Shape permits Circle, Square, Triangle {}
public record Circle(double radius) implements Shape {
public double area() { return Math.PI * radius * radius; }
}
public record Square(double side) implements Shape {
public double area() { return side * side; }
}
public record Triangle(double base, double height) implements Shape {
public double area() { return base * height / class="tok-num">2; }
}
class=class="tok-str">"tok-cmt">// exhaustive switch - no default needed
public double area(Shape s) {
return switch (s) {
case Circle c -> c.area();
case Square s2 -> s2.area();
case Triangle t -> t.area();
class=class="tok-str">"tok-cmt">// no default - compiler knows all permitted subclasses
};
}
Combined with pattern matching for switch, sealed hierarchies let the compiler verify exhaustiveness: if you add a new permitted subclass, every switch over the hierarchy becomes a compile error until you handle the new case.
7. Pitfalls of Inheritance
A Stack is not a Vector — it has one. Use composition (a private field of the helper type) instead of inheritance for “has-a” relationships.
A subclass depends on the implementation details of its parent. If the parent evolves, the subclass can break. The classic example is HashSet vs a subclass that overrides add to count insertions — the parent's internal addAll calls add, leading to double-counting. Prefer composition plus interfaces unless you control both classes.
Exercises
- Create a
Vehiclebase class with fieldsspeedandbrand, plus amove()method. Subclass it asCarandBicycle, overridingmove(). - Override
toStringin aPersonclass to print “Person[name=X, age=Y]”. - Build a sealed hierarchy
ShapepermittingCircle,Square,Triangle, and write a switch expression returning the area. - Make a
finalclassMoneyand explain why you would not want it subclassable.