Skip to content
Laviyx Learn

Try

Java course menu

Java Inheritance: extends, super, Method Overriding and Object

Learn inheritance in Java: extending a class with extends, calling the parent constructor with super(...), overriding methods with @Override, calling super.method(), protected members, the Object class, final classes and methods, and composition vs inheritance.

Intermediate12 min readUpdated
On this page

Inheritance lets a new class build on an existing one. The subclass (child) gets the fields and methods of the superclass (parent), and adds or changes what it needs. Use it for an "is-a" relationship: a SavingsAccount is an Account, a Car is a Vehicle.

extends and super(...)#

Java
class Vehicle {
    protected final String brand;
    protected final int wheels;

    Vehicle(String brand, int wheels) {
        this.brand = brand;
        this.wheels = wheels;
    }

    void describe() {
        System.out.println(brand + " with " + wheels + " wheels");
    }
}

class Car extends Vehicle {
    private final int seats;

    Car(String brand, int seats) {
        super(brand, 4);          // call the Vehicle constructor first
        this.seats = seats;
    }

    void honk() {
        System.out.println(brand + " says beep!");   // brand is protected: accessible here
    }

    int getSeats() {
        return seats;
    }
}

public class Main {
    public static void main(String[] args) {
        Car car = new Car("Tata", 5);
        car.describe();          // inherited
        car.honk();              // Car's own method
        System.out.println(car.getSeats() + " seats");
    }
}
Output
Tata with 4 wheels
Tata says beep!
5 seats
  • class Car extends Vehicle makes Car a subclass of Vehicle.
  • super(...) calls the parent's constructor and must be the first statement. If you leave it out, Java calls the parent's no-argument constructor, which fails to compile if there is none.
  • Java has single inheritance of classes: a class extends exactly one parent. (A class can implement many interfaces; see Interfaces.)

Overriding methods#

A subclass can replace an inherited method by declaring one with the same signature. Mark it @Override so the compiler checks you really are overriding:

Java
class Employee {
    protected final String name;
    protected final double salary;

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

    double pay() {
        return salary;
    }
}

class Manager extends Employee {
    private final double bonus;

    Manager(String name, double salary, double bonus) {
        super(name, salary);
        this.bonus = bonus;
    }

    @Override
    double pay() {
        return super.pay() + bonus;   // reuse the parent's version
    }
}

public class Main {
    public static void main(String[] args) {
        Employee e = new Employee("Ravi", 50000);
        Employee m = new Manager("Meera", 80000, 15000);   // a Manager IS an Employee
        System.out.println(e.name + ": " + e.pay());
        System.out.println(m.name + ": " + m.pay());
    }
}
Output
Ravi: 50000.0
Meera: 95000.0

super.pay() calls the parent's implementation. Note that m is declared as an Employee but m.pay() still runs Manager.pay(): in Java, every instance method is chosen at run time by the object's actual class. That is polymorphism, covered in the next lesson.

Rules for overriding: same name and parameters; the return type must be the same (or a subtype); you cannot make the method less visible; and static, private and final methods cannot be overridden.

Construction order#

The parent part of an object is initialized first:

Java
class Base {
    Base() { System.out.println("Base constructor"); }
}

class Derived extends Base {
    Derived() {
        super();   // implicit if omitted
        System.out.println("Derived constructor");
    }
}

public class Main {
    public static void main(String[] args) {
        new Derived();
    }
}
Output
Base constructor
Derived constructor

protected#

protected members are visible to subclasses (and to classes in the same package), but not to unrelated code. They are convenient, but every subclass can then depend on, and break, those details. Many developers keep fields private and expose protected methods instead.

The Object class#

Every class implicitly extends java.lang.Object, so every object has its methods, the most important being:

MethodPurpose
toString()A readable description (used by println)
equals(Object o)Content equality (default: same object)
hashCode()A hash code consistent with equals (needed for HashMap/HashSet keys)
getClass()The object's run-time class

If you override equals, you must also override hashCode, or hash-based collections misbehave. Records generate both for you.

final classes and methods#

  • A final method cannot be overridden.
  • A final class cannot be extended. String is final, which is part of what makes it reliably immutable.

Composition over inheritance#

Inheritance is right only for a true "is-a" relationship. When one object has or uses another, make it a field instead (composition):

Java
class Engine {
    void start() { System.out.println("engine started"); }
}

class Car {
    private final Engine engine = new Engine();   // a Car HAS an Engine

    void drive() {
        engine.start();
        System.out.println("driving");
    }
}

public class Main {
    public static void main(String[] args) {
        new Car().drive();
    }
}
Output
engine started
driving

"Prefer composition over inheritance" is classic advice from Effective Java: composition is more flexible, and subclasses do not break when the parent class changes.

Common mistakes#

Practice#

Practice exercise

Create a class Account with an owner and a balance, a deposit method and getters. Extend it with SavingsAccount, which adds an interest rate and addInterest() that deposits balance * rate. Create a savings account for "Asha" with 10000 at 4%, add interest, and print the balance.

Show solution
Java
class Account {
    private final String owner;
    private double balance;

    Account(String owner, double balance) {
        this.owner = owner;
        this.balance = balance;
    }

    void deposit(double amount) {
        if (amount > 0) balance += amount;
    }

    double getBalance() { return balance; }
    String getOwner() { return owner; }
}

class SavingsAccount extends Account {
    private final double rate;

    SavingsAccount(String owner, double balance, double rate) {
        super(owner, balance);
        this.rate = rate;
    }

    void addInterest() {
        deposit(getBalance() * rate);
    }
}

public class Bank {
    public static void main(String[] args) {
        SavingsAccount s = new SavingsAccount("Asha", 10000, 0.04);
        s.addInterest();
        System.out.println(s.getOwner() + ": " + s.getBalance());
    }
}
Output
Asha: 10400.0

SavingsAccount uses only the public interface of Account, so the parent keeps full control of the balance.

Key takeaway#

Finished this lesson?