Inheritance

Su Suriya Ravichandran Updated 14 Nov 2026
8 min read ·Lesson 51 of 133

Inheritance lets one class build on another. The new class (the subclass, or child) automatically receives the fields and methods of an existing class (the superclass, or parent), and can add new members or change inherited behavior. It models "is-a" relationships, such as a manager is an employee and a savings account is an account, and lets shared code be written once.

Basic Syntax: extends

public class Vehicle {
    protected final String registration;
    protected int speed;

    public Vehicle(String registration) {
        this.registration = registration;
    }

    public void accelerate(int amount) {
        speed += amount;
    }

    public String describe() {
        return registration + " moving at " + speed + " km/h";
    }
}

public class Truck extends Vehicle {
    private final double maxLoadTons;
    private double currentLoad;

    public Truck(String registration, double maxLoadTons) {
        super(registration);
        this.maxLoadTons = maxLoadTons;
    }

    public void load(double tons) {
        if (currentLoad + tons > maxLoadTons) {
            throw new IllegalStateException("overloaded");
        }
        currentLoad += tons;
    }
}
Truck t = new Truck("TN-09-AB-1234", 12);
t.load(8);              // defined in Truck
t.accelerate(40);       // inherited from Vehicle
System.out.println(t.describe());   // inherited: TN-09-AB-1234 moving at 40 km/h

Truck extends Vehicle means a Truck has everything a Vehicle has (registration, speed, accelerate, describe) plus what Truck adds (maxLoadTons, currentLoad, load). The code for accelerating and describing exists only in Vehicle, and every kind of vehicle reuses it.

What Is Inherited

A subclass inherits all non-private members of its superclass: public and protected fields and methods, and package-private ones when both classes are in the same package.

Member in the superclassAccessible in the subclass?
publicYes
protectedYes, even from a different package
package-private (no modifier)Only if the subclass is in the same package
privateNo (it still exists in the object, but cannot be named)
ConstructorsNever inherited
static membersAccessible, but belong to the class, not inherited per object

Private fields deserve a closer look. A Truck object really does contain the private fields declared in Vehicle; they are part of its memory. Truck's code simply cannot access them by name. It must use the methods Vehicle provides, which keeps Vehicle in control of its own data:

public class Vehicle {
    private int speed;
    public int getSpeed() { return speed; }
    public void accelerate(int amount) { speed += amount; }
}

public class Truck extends Vehicle {
    public void report() {
        System.out.println(speed);        // compile error: speed has private access
        System.out.println(getSpeed());   // fine
    }
}

protected fields are convenient, but they let every subclass bypass the parent's validation. Private fields with protected or public methods are usually safer.

Constructors and super(...)

Constructors are not inherited. Each subclass constructor must make sure the superclass part of the object is initialized, by calling a superclass constructor with super(...):

public Truck(String registration, double maxLoadTons) {
    super(registration);                 // initialize the Vehicle part
    this.maxLoadTons = maxLoadTons;      // then the Truck part
}

If a constructor does not call super(...) explicitly, the compiler inserts super(), a call to the parent's no-argument constructor. When the parent has no such constructor, compilation fails:

public class Truck extends Vehicle {
    public Truck() { }    // compile error: constructor Vehicle in class Vehicle cannot be applied to given types
}

Construction always proceeds from the top of the hierarchy down: Object, then Vehicle, then Truck. A subclass can rely on its parent being fully initialized by the time its own constructor body runs.

Overriding Methods

A subclass can provide its own version of an inherited method, with the same name, parameters, and a compatible return type. This is overriding:

public class Truck extends Vehicle {
    // ...

    @Override
    public String describe() {
        return super.describe() + ", carrying " + currentLoad + "/" + maxLoadTons + " t";
    }
}
System.out.println(t.describe());
// TN-09-AB-1234 moving at 40 km/h, carrying 8.0/12.0 t

super.describe() calls the parent's version, and the override adds to its result rather than duplicating it. The @Override annotation makes the compiler verify that a matching parent method exists; a typo such as descibe() would otherwise silently create a new, unrelated method. Overriding has its own lesson.

Inheritance Creates an "is-a" Relationship

A subclass object can be used wherever its superclass type is expected:

Vehicle v = new Truck("KA-01-XY-9876", 10);   // a Truck is a Vehicle
v.accelerate(30);
System.out.println(v.describe());              // runs Truck's describe()

List<Vehicle> fleet = List.of(
    new Vehicle("TN-01-AA-0001"),
    new Truck("TN-09-AB-1234", 12)
);
for (Vehicle each : fleet) {
    System.out.println(each.describe());
}

The variable's declared type, Vehicle, determines which methods can be called: v.load(5) does not compile, because not every Vehicle has load. The object's actual class, Truck, determines which implementation runs. This is polymorphism, the subject of the next lesson.

The Object Class

Every class that does not explicitly extend another implicitly extends java.lang.Object. So every class inherits:

  • toString()
  • equals(Object) and hashCode()
  • getClass()
  • wait(), notify(), notifyAll() (thread coordination)

This is why any object can be printed, put in a collection, or compared with equals, and why these methods are so often overridden.

Single Inheritance and Hierarchies

A Java class extends exactly one superclass. Multiple inheritance of classes is not allowed:

public class AmphibiousCar extends Car, Boat { }   // compile error

Allowing two parents would create ambiguity whenever both define the same method or field (the "diamond problem"). Java instead allows a class to implement any number of interfaces, which provide multiple inheritance of type without inheriting conflicting state.

Inheritance can extend over several levels:

Object
  └── Vehicle
        ├── Car
        │     └── ElectricCar
        └── Truck

ElectricCar inherits from Car, which inherits from Vehicle, which inherits from Object. Deep hierarchies become hard to understand, since behavior may be spread across many levels. Two or three levels below Object is usually plenty.

Preventing Inheritance

  • A final class cannot be extended (String, Integer, LocalDate).
  • A final method cannot be overridden.
  • A sealed class (Java 17+) restricts which classes may extend it:
public sealed class Payment permits CardPayment, UpiPayment, CashPayment { }

public final class CardPayment extends Payment { }
public final class UpiPayment extends Payment { }
public final class CashPayment extends Payment { }

Only the three listed classes may extend Payment. Each permitted subclass must declare itself final, sealed, or non-sealed. The compiler then knows the complete set of subtypes, which allows exhaustive switch expressions over them without a default branch.

When to Use Inheritance

Use inheritance when:

  • There is a genuine "is-a" relationship: every Truck really is a Vehicle and can be used anywhere a Vehicle is expected.
  • Subclasses share real behavior with the parent, not just a few fields.
  • The parent class was designed to be extended.

Prefer composition ("has-a") when the relationship is about using another object's capabilities:

// Inheritance misused: a Stack is not a kind of ArrayList
public class Stack<T> extends ArrayList<T> { ... }
// Callers can now call add(0, x) or remove(5), breaking stack behavior

// Composition: a Stack has a list it uses internally
public class Stack<T> {
    private final List<T> items = new ArrayList<>();
    public void push(T item) { items.add(item); }
    public T pop() { return items.remove(items.size() - 1); }
    public boolean isEmpty() { return items.isEmpty(); }
}

The inheritance version exposes every ArrayList method, so callers can insert into the middle of the "stack." The composition version exposes only stack operations. The JDK's own java.util.Stack extends Vector and suffers from exactly this problem, which is one reason ArrayDeque is recommended instead.

A useful test: if a subclass needs to disable or work around inherited methods, inheritance is the wrong tool.

Practical Example: Employee Payroll

import java.util.List;

public class Payroll {
    static class Employee {
        private final String name;
        private final double baseMonthly;

        Employee(String name, double baseMonthly) {
            this.name = name;
            this.baseMonthly = baseMonthly;
        }

        public String getName() { return name; }
        protected double getBaseMonthly() { return baseMonthly; }

        public double monthlyPay() {
            return baseMonthly;
        }

        @Override
        public String toString() {
            return String.format("%-8s %-10s %10.2f",
                    getClass().getSimpleName(), name, monthlyPay());
        }
    }

    static class Manager extends Employee {
        private final double teamBonus;

        Manager(String name, double baseMonthly, double teamBonus) {
            super(name, baseMonthly);
            this.teamBonus = teamBonus;
        }

        @Override
        public double monthlyPay() {
            return super.monthlyPay() + teamBonus;
        }
    }

    static class Intern extends Employee {
        Intern(String name, double stipend) {
            super(name, stipend);
        }

        @Override
        public double monthlyPay() {
            return getBaseMonthly() * 0.9;        // 10% held for course fees
        }
    }

    public static void main(String[] args) {
        List<Employee> staff = List.of(
            new Employee("Ravi", 60_000),
            new Manager("Meena", 95_000, 15_000),
            new Intern("Arjun", 20_000)
        );

        double total = 0;
        for (Employee e : staff) {
            System.out.println(e);
            total += e.monthlyPay();
        }
        System.out.printf("Total payroll: %.2f%n", total);
    }
}

Output:

Employee Ravi         60000.00
Manager  Meena       110000.00
Intern   Arjun        18000.00
Total payroll: 188000.00

Manager and Intern inherit the name, base pay, and toString from Employee; each adds only what is different. Manager.monthlyPay builds on the parent's calculation through super.monthlyPay(). Intern reaches the private base amount through the protected getter rather than the field. The payroll loop treats every person as an Employee, yet each object computes its own pay, and toString (written once in Employee) automatically reports the correct class name and amount through getClass() and the overridden monthlyPay().

0 Comments

Reviewed before they appear

No comments yet.

Java
Java Introduction Java Features Java Syntax Java Comments Java Statements Java Output Java Variables Java Data Types Java Type Casting Java Operators Java Strings Java Numbers Java Math Java Booleans Java User Input Java Conditions Java If Else Java Switch Java While Loop Java For Loop Java Break Java Continue Java Arrays Java Methods Java Parameters Java Return Values Java Scope Java Recursion Install Java Install JDK Java IDEs Java JDK Java JRE Java JVM Java Compiler Java File Compile Java Run Java JShell Basics Java Packages Java OOP Java Classes Java Objects Class Methods Constructors Constructor Overload this Keyword static Keyword final Keyword Encapsulation Inheritance Polymorphism Abstraction Access Modifiers super Keyword Method Overload Method Override Instanceof Operator Nested Classes Inner Classes Anonymous Classes Enums Records Array Basics Array Length Loop Arrays Multidimensional Arrays Array Copy Array Sorting Array Search ArrayList LinkedList HashSet TreeSet HashMap TreeMap Queue Interface Deque Interface Stack Class Iterator List Interface Set Interface Map Interface Collections Class Comparable Comparator Generics Basics Generic Classes Generic Methods Wildcards String Basics String Length String Methods String Concatenation String Compare String Search String Replace String Split String Format StringBuilder StringBuffer Text Blocks Regular Expressions Number Types Integer Class Float Class Double Class BigInteger BigDecimal Math Class Random Numbers Number Format Date Basics LocalDate LocalTime LocalDateTime ZonedDateTime Date Formatting Date Parsing Period Class Duration Class Java Instant Class Java Exception Basics Java try...catch Java Multiple Catch Blocks Java finally Block Java throw Keyword Java throws Keyword Java Checked Exceptions Java Unchecked Exceptions Java Custom Exceptions Java try-with-resources Java Exception Chaining
Ask about this post
AI Ask about this post

Ask questions about Inheritance and get answers drawn from it.

Signed-in readers only.