Home/Learn/Java A–Z/Inheritance

Inheritance

Beginner
Java Fundamentals

Use extends to build class hierarchies, override methods with @Override, call parent behaviour with super, and know when to prefer composition.

Overview

Inheritance lets a child class reuse and extend the behaviour of a parent class. Java supports single class inheritance (one extends) but multiple interface implementation. The @Override annotation is your safety net — the compiler verifies the method signature matches. super calls the parent's constructor or method. Every Java class implicitly extends Object, giving every object toString(), equals(), hashCode(), and wait/notify for threading. The composition-over-inheritance principle says: prefer has-a relationships over is-a when the relationship isn't truly hierarchical.

extends, @Override & super

The child class inherits all non-private members of the parent. Overriding replaces the parent's method with a new implementation — the method signature must match exactly (use @Override to get a compile error if it doesn't).

super.methodName() calls the parent's version of a method. super() in a constructor calls the parent constructor and must be the first statement. If you don't write super(), the compiler inserts super() automatically — so the parent must have a no-arg constructor.

Animal.java / Dog.java
public class Animal {
    protected String name;

    public Animal(String name) {
        this.name = name;
    }

    public String speak() {
        return name + " makes a sound";
    }

    public String describe() {
        return "I am " + name;
    }
}

public class Dog extends Animal {
    private String breed;

    public Dog(String name, String breed) {
        super(name);           // must call parent constructor first
        this.breed = breed;
    }

    @Override                  // compiler verifies signature matches
    public String speak() {
        return name + " barks!";
    }

    @Override
    public String describe() {
        return super.describe() + ", a " + breed;  // reuse + extend
    }

    public static void main(String[] args) {
        Dog dog = new Dog("Rex", "Labrador");
        System.out.println(dog.speak());    // Rex barks!
        System.out.println(dog.describe()); // I am Rex, a Labrador

        // Polymorphic reference — Animal variable holds a Dog
        Animal a = new Dog("Buddy", "Poodle");
        System.out.println(a.speak());      // Buddy barks! (runtime dispatch)
        System.out.println(a instanceof Dog); // true
    }
}

The Object Class & Covariant Return Types

Object is the root of every Java class hierarchy. Key methods inherited by all objects:

toString() — called implicitly by println and string concatenation equals(Object) + hashCode() — must be overridden together getClass() — returns the runtime Class object clone() — protected; implement Cloneable to expose finalize() — deprecated; don't rely on it wait() / notify() / notifyAll() — low-level thread synchronisation

Covariant return type: an overriding method may return a more specific type than the parent declared. This is useful for builder patterns and factory methods.

Shape.java / Circle.java
public class Shape {
    public Shape copy() {
        return new Shape(); // returns Shape
    }
    @Override
    public String toString() { return "Shape"; }
}

public class Circle extends Shape {
    private double radius;
    public Circle(double r) { this.radius = r; }

    // Covariant return — Circle is more specific than Shape
    @Override
    public Circle copy() {
        return new Circle(radius);  // allowed in Java
    }

    @Override
    public String toString() { return "Circle(r=" + radius + ")"; }

    public static void main(String[] args) {
        Circle c = new Circle(5.0);
        Circle c2 = c.copy();     // no cast needed — covariant return
        System.out.println(c2);   // Circle(r=5.0)

        // getClass() and instanceof
        System.out.println(c.getClass().getSimpleName()); // Circle
        System.out.println(c instanceof Shape);           // true
        System.out.println(c instanceof Circle);          // true
    }
}

Composition vs Inheritance

Inheritance models an IS-A relationship: a Dog IS-A Animal. Composition models a HAS-A relationship: a Car HAS-A Engine. Prefer composition when:

• The relationship is not truly hierarchical • You want to reuse behaviour from multiple sources (Java only allows one superclass) • You want to change the implementation at runtime (strategy pattern) • The subclass breaks the Liskov Substitution Principle (callers cannot safely substitute the subclass for the parent)

Favour composition over inheritance is one of the core principles of the Gang of Four design patterns book.

CompositionExample.java
// Inheritance — tight coupling, fragile
class ElectricCar extends Battery { }  // BAD: Car IS-A Battery? No.

// Composition — flexible, correct
public class Engine {
    public void start() { System.out.println("Engine started"); }
}

public class GPS {
    public String getLocation() { return "37.7749° N, 122.4194° W"; }
}

public class Car {
    private final Engine engine = new Engine(); // HAS-A Engine
    private final GPS    gps    = new GPS();    // HAS-A GPS

    public void drive() {
        engine.start();
        System.out.println("Driving to " + gps.getLocation());
    }
}

// Real-world composition example: Stack built on top of Deque
import java.util.ArrayDeque;
import java.util.Deque;

public class Stack<T> {
    private final Deque<T> deque = new ArrayDeque<>(); // composed, not extended

    public void push(T item) { deque.push(item); }
    public T pop()           { return deque.pop(); }
    public T peek()          { return deque.peek(); }
    public boolean isEmpty() { return deque.isEmpty(); }
}

Interactive Visualization

C = ClassA = AbstractI = Interface
CObject
Object is the root of the Java class hierarchy. Every class extends Object.
1 / 5

Key Points to Remember

  • Java supports single inheritance for classes; use interfaces for multiple type inheritance
  • @Override makes the compiler verify the signature — always use it when overriding
  • super() to call parent constructor must be the first line; compiler inserts it if missing
  • Method dispatch is decided at runtime based on the actual object type (dynamic dispatch)
  • Covariant return types let overriding methods return a more specific type
  • Prefer composition over inheritance when the IS-A relationship is not genuine

Practice Inheritance in the Playground

Run and modify code directly in your browser - no setup needed.

Interview Questions

Sign in to ask Aria
1

What is the difference between method overloading and method overriding?

EasyTCS
2

Can you override a private method in Java?

EasyWipro
3

What is the Liskov Substitution Principle? Give an example of a violation.

HardGoogle
4

Why does Java not support multiple inheritance for classes?

MediumOracle
5

When would you choose composition over inheritance?

MediumAmazon

Ask Aria about Inheritance

Your personal AI tutor — ask anything about this concept