Skip to content

Java Inheritance

Inheritance is a fundamental pillar of Object-Oriented Programming (OOP) in Java. It allows a class (the subclass or child class) to inherit fields and methods from another class (the superclass or parent class).

This mechanism promotes code reuse (avoiding duplication) and establishes a hierarchical relationship between classes, often referred to as an “IS-A” relationship.

The “IS-A” Relationship (Class Inheritance)

Section titled “The “IS-A” Relationship (Class Inheritance)”

The primary keyword for class inheritance in Java is extends. When a class B extends class A, it means that B IS-A type of A. The subclass B inherits all non-private members (fields and methods) of the superclass A.

Example Hierarchy:

// Superclass
class Vehicle {
String brand;
int year;
public Vehicle(String brand, int year) {
this.brand = brand;
this.year = year;
}
void displayInfo() {
System.out.println("Brand: " + brand + ", Year: " + year);
}
void startEngine() {
System.out.println("Vehicle engine starting...");
}
}
// Subclass inheriting from Vehicle
class Car extends Vehicle {
int numberOfDoors;
public Car(String brand, int year, int doors) {
// Call the superclass constructor using super()
// Must be the first statement in the subclass constructor
super(brand, year);
this.numberOfDoors = doors;
}
// Method specific to Car
void honk() {
System.out.println("Car horn: Beep beep!");
}
// Overriding a method from the superclass
@Override // Good practice annotation
void startEngine() {
System.out.println("Car engine starting with a key turn...");
}
}
// Another subclass
class Bicycle extends Vehicle { // Bicycles can also be considered Vehicles in this model
boolean hasBell;
public Bicycle(String brand, int year, boolean bell) {
super(brand, year);
this.hasBell = bell;
}
@Override
void startEngine() {
System.out.println("Bicycle doesn't have an engine!"); // Specific behavior
}
void ringBell() {
if(hasBell) {
System.out.println("Bicycle bell: Ring ring!");
} else {
System.out.println("This bicycle has no bell.");
}
}
}

In this example:

  • Vehicle is the superclass.
  • Car and Bicycle are subclasses of Vehicle.
  • Car IS-A Vehicle. It inherits brand, year, displayInfo(), and startEngine() from Vehicle.
  • Car adds its own field (numberOfDoors) and method (honk()).
  • Car overrides the startEngine() method to provide a car-specific implementation.
  • Bicycle also IS-A Vehicle and overrides startEngine() differently.
  • Subclasses use super(arguments) to call the superclass constructor.
  • The @Override annotation indicates the intent to override a superclass method and helps the compiler catch errors (e.g., typos in the method signature).

The instanceof operator checks if an object is an instance of a particular class or an instance of a subclass of that class.

public class InheritanceCheck {
public static void main(String[] args) {
Vehicle generalVehicle = new Vehicle("Generic", 2020);
Car myCar = new Car("Toyota", 2022, 4);
Bicycle myBike = new Bicycle("Giant", 2023, true);
System.out.println("myCar instanceof Car: " + (myCar instanceof Car)); // true
System.out.println("myCar instanceof Vehicle: " + (myCar instanceof Vehicle)); // true (IS-A relationship)
System.out.println("myBike instanceof Bicycle: " + (myBike instanceof Bicycle)); // true
System.out.println("myBike instanceof Vehicle: " + (myBike instanceof Vehicle)); // true (IS-A relationship)
System.out.println("generalVehicle instanceof Car: " + (generalVehicle instanceof Car)); // false
}
}

Inheritance enables polymorphism. You can refer to a subclass object using a superclass reference variable. When you call an overridden method on the superclass reference, Java executes the subclass’s version of the method (dynamic method dispatch).

// Continuing the InheritanceCheck example:
Vehicle vehicleRef1 = myCar; // Car object referenced by Vehicle variable
Vehicle vehicleRef2 = myBike; // Bicycle object referenced by Vehicle variable
vehicleRef1.startEngine(); // Calls Car's overridden startEngine() -> Car engine starting...
vehicleRef2.startEngine(); // Calls Bicycle's overridden startEngine() -> Bicycle doesn't have an engine!
vehicleRef1.displayInfo(); // Calls Vehicle's displayInfo() (not overridden)
// vehicleRef1.honk(); // Compile Error! honk() is not defined in the Vehicle class.

Inheritance and Interfaces (The implements Keyword)

Section titled “Inheritance and Interfaces (The implements Keyword)”

While a class can extend only one superclass (Java supports single class inheritance), it can implement multiple interfaces.

An interface defines a contract - a set of abstract methods (and potentially default/static methods since Java 8) that implementing classes must provide.

Example using Interface:

interface Drivable {
void drive(); // Abstract method (no body)
int MAX_SPEED = 120; // Constants are implicitly public static final
}
interface Chargeable {
void charge();
}
// Car now extends Vehicle and implements Drivable
class ElectricCar extends Vehicle implements Drivable, Chargeable {
int batteryCapacity;
public ElectricCar(String brand, int year, int capacity) {
super(brand, year);
this.batteryCapacity = capacity;
}
@Override
public void drive() { // Must implement drive() from Drivable
System.out.println("Electric car is driving silently...");
}
@Override
public void charge() { // Must implement charge() from Chargeable
System.out.println("Plugging in the electric car to charge...");
}
@Override // Override from Vehicle
void startEngine() {
System.out.println("Electric car motor starting up quietly...");
}
}
public class InterfaceCheck {
public static void main(String[] args) {
ElectricCar tesla = new ElectricCar("Tesla", 2023, 100);
System.out.println("tesla instanceof ElectricCar: " + (tesla instanceof ElectricCar)); // true
System.out.println("tesla instanceof Vehicle: " + (tesla instanceof Vehicle)); // true (via extends)
System.out.println("tesla instanceof Drivable: " + (tesla instanceof Drivable)); // true (via implements)
System.out.println("tesla instanceof Chargeable: " + (tesla instanceof Chargeable)); // true (via implements)
tesla.startEngine();
tesla.drive();
tesla.charge();
}
}

Implementing interfaces also establishes an IS-A relationship (e.g., ElectricCar IS-A Drivable).

The “HAS-A” Relationship (Composition)

Section titled “The “HAS-A” Relationship (Composition)”

While inheritance represents an “IS-A” relationship, sometimes you need to model a “HAS-A” relationship. This is achieved through composition, where a class contains an instance (a field) of another class.

Composition is often preferred over inheritance when the relationship isn’t strictly “IS-A”, as it offers more flexibility and avoids tight coupling.

Example: A Car HAS-A Engine

class Engine {
private String type;
private int horsepower;
public Engine(String type, int hp) {
this.type = type;
this.horsepower = hp;
}
public void start() {
System.out.println("Engine (" + type + ", " + horsepower + "hp) starting...");
}
public void stop() {
System.out.println("Engine stopping...");
}
}
// Car class using composition (HAS-A Engine)
class CarWithEngine {
private String make;
private String model;
private Engine engine; // Car HAS-A Engine
public CarWithEngine(String make, String model, Engine engine) {
this.make = make;
this.model = model;
this.engine = engine; // Assign the Engine object
}
public void startCar() {
System.out.println("Starting the " + make + " " + model + ".");
engine.start(); // Delegate the start action to the Engine object
}
public void stopCar() {
System.out.println("Stopping the " + make + " " + model + ".");
engine.stop(); // Delegate stop action
}
// Getter for the engine if needed
public Engine getEngine() {
return engine;
}
}
public class CompositionDemo {
public static void main(String[] args) {
Engine v6Engine = new Engine("V6", 300);
CarWithEngine mySedan = new CarWithEngine("Honda", "Accord", v6Engine);
mySedan.startCar();
mySedan.stopCar();
}
}

Here, CarWithEngine doesn’t inherit from Engine. Instead, it holds a reference to an Engine object and delegates engine-related tasks to it. This promotes code reuse (the Engine class can be used elsewhere) and flexibility.

  • Inheritance (extends) models an “IS-A” relationship, promoting code reuse and polymorphism.
  • Java supports single class inheritance but multiple interface implementation (implements).
  • Use super() to call superclass constructors and super.methodName() to call superclass methods.
  • Use @Override when intending to override a superclass method.
  • Composition (having an instance of another class as a field) models a “HAS-A” relationship and is often favored for flexibility.
  • instanceof checks the type relationship between an object and a class/interface.