Updated: July 23, 2025

Inheritance is one of the fundamental concepts of object-oriented programming (OOP) and plays a crucial role in Java. It allows a new class to inherit properties and behaviors (fields and methods) from an existing class. This promotes code reusability, improves maintainability, and establishes a natural hierarchy between classes.

In this article, we will explore how to implement inheritance in Java programming. We will cover key concepts, syntax, practical examples, and best practices to help you master inheritance effectively.

What is Inheritance in Java?

Inheritance enables the creation of a new class based on an existing class. The existing class is called the superclass (or parent class), and the new class is called the subclass (or child class). The subclass inherits all accessible members (fields and methods) from the superclass, allowing it to reuse code and extend functionality.

Why Use Inheritance?

  • Code Reusability: Avoid duplicating code by inheriting common functionality.
  • Method Overriding: Customize or extend behavior inherited from the superclass.
  • Polymorphism: Treat objects of different subclasses as instances of the superclass.
  • Hierarchical Classification: Represent real-world relationships between entities.

Basic Syntax of Inheritance in Java

Java uses the extends keyword to establish an inheritance relationship between two classes:

class Superclass {
    // Fields and methods
}

class Subclass extends Superclass {
    // Additional fields and methods
}

Here, Subclass inherits all accessible (non-private) members from Superclass.

Types of Inheritance Supported in Java

Java supports several forms of inheritance:

  • Single Inheritance: A subclass inherits from one superclass.
  • Multilevel Inheritance: A subclass inherits from a superclass, which itself inherits from another class.
  • Hierarchical Inheritance: Multiple subclasses inherit from a single superclass.

Java does not support multiple inheritance (a subclass inheriting from multiple superclasses) with classes to avoid ambiguity but provides multiple inheritance with interfaces.

Access Modifiers and Inheritance

Access modifiers determine which members are inherited:

Modifier Accessible in Subclass? Description
public Yes Accessible everywhere
protected Yes Accessible within package & subclasses
default (no modifier) Yes if same package Accessible only within package
private No Not accessible outside superclass

Understanding access control is critical for designing inheritance hierarchies that encapsulate behavior properly.

Implementing Single Inheritance: Step-by-Step

Let’s create a simple example demonstrating single inheritance.

Step 1: Define the Superclass

public class Animal {
    String name;

    public void eat() {
        System.out.println(name + " is eating.");
    }
}

Step 2: Define the Subclass That Extends the Superclass

public class Dog extends Animal {

    public void bark() {
        System.out.println(name + " is barking.");
    }
}

Step 3: Using the Subclass

public class TestInheritance {
    public static void main(String[] args) {
        Dog myDog = new Dog();
        myDog.name = "Buddy";          // Access field inherited from Animal
        myDog.eat();                   // Call method inherited from Animal
        myDog.bark();                  // Call subclass-specific method
    }
}

Output:

Buddy is eating.
Buddy is barking.

Here, Dog inherits the name field and eat() method from Animal. The subclass adds its unique behavior with bark().

Multilevel Inheritance Example

In multilevel inheritance, a subclass serves as a superclass for another subclass.

// Level 1: Superclass
class Vehicle {
    void start() {
        System.out.println("Vehicle started");
    }
}

// Level 2: Subclass of Vehicle
class Car extends Vehicle {
    void drive() {
        System.out.println("Car is driving");
    }
}

// Level 3: Subclass of Car
class SportsCar extends Car {
    void turboBoost() {
        System.out.println("SportsCar Turbo Boost activated!");
    }
}

public class Main {
    public static void main(String[] args) {
        SportsCar sc = new SportsCar();
        sc.start();       // Method from Vehicle
        sc.drive();       // Method from Car
        sc.turboBoost();  // Method from SportsCar
    }
}

Output:

Vehicle started
Car is driving
SportsCar Turbo Boost activated!

Method Overriding in Inheritance

Subclasses can provide their own implementation for methods inherited from the superclass. This is called method overriding and is useful for polymorphic behavior.

Example:

class Animal {
    void sound() {
        System.out.println("Animal makes a sound");
    }
}

class Cat extends Animal {
    @Override
    void sound() {
        System.out.println("Cat meows");
    }
}

public class TestOverride {
    public static void main(String[] args) {
        Animal myAnimal = new Animal();
        Animal myCat = new Cat();

        myAnimal.sound();   // Outputs: Animal makes a sound
        myCat.sound();      // Outputs: Cat meows (runtime polymorphism)
    }
}

The @Override annotation helps ensure that you are indeed overriding a method from the superclass.

Using super Keyword

The keyword super refers to the immediate superclass. It can be used to:

  • Call superclass constructors.
  • Access superclass methods.
  • Access superclass fields if hidden by subclass fields.

Example: Calling Superclass Constructor

class Person {
    String name;

    Person(String name) {
        this.name = name;
    }
}

class Employee extends Person {
    int employeeId;

    Employee(String name, int employeeId) {
        super(name);               // Call Person constructor
        this.employeeId = employeeId;
    }

    void display() {
        System.out.println("Name: " + name + ", ID: " + employeeId);
    }
}

Using super() ensures proper initialization through constructor chaining.

Constructors and Inheritance

Constructors are not inherited but are called during object creation:

  • The first line of every constructor calls either another constructor of the same class (this(...)) or a constructor of its immediate superclass (super(...)).
  • If you don’t explicitly call a superclass constructor, Java automatically inserts a no-argument call to the superclass constructor (super()), if available.

Example showing constructor chaining:

class A {
    A() {
        System.out.println("Constructor of A");
    }
}

class B extends A {
    B() {
        super();   // Calls A's constructor explicitly (optional if no-arg)
        System.out.println("Constructor of B");
    }
}

public class TestConstructorInheritance {
    public static void main(String[] args) {
        B bObj = new B();
    }
}

Output:

Constructor of A
Constructor of B

Abstract Classes and Inheritance

An abstract class cannot be instantiated but can be extended. It can contain abstract methods (without implementation) that must be overridden by subclasses. This enforces certain behaviors for subclasses.

abstract class Animal {
    abstract void makeSound();

    void sleep() {
        System.out.println("Sleeping...");
    }
}

class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("Dog barks");
    }
}

public class AbstractDemo {
    public static void main(String[] args) {
        Dog d = new Dog();
        d.makeSound();
        d.sleep();
    }
}

Output:

Dog barks
Sleeping...

Abstract classes are widely used when you want to define a base template with some common code while forcing subclasses to provide specific implementations.

Final Classes and Methods

  • Declaring a class as final means it cannot be subclassed.
  • Declaring a method as final means it cannot be overridden.

This is useful for security reasons or design constraints.

final class Calculator {}

// The following will cause compile-time error:
// class AdvancedCalculator extends Calculator {}

class A {
   final void show() {}
}

class B extends A {
// The following will cause error:
// void show() {}
}

Best Practices for Using Inheritance in Java

  1. Favor Composition Over Inheritance: Use inheritance only when there is an “is-a” relationship. Otherwise, prefer composition (“has-a” relationship).
  2. Keep Hierarchies Simple: Deep inheritance trees can become hard to maintain.
  3. Use Interfaces When Appropriate: Use interfaces for defining contracts especially when multiple inheritance-like behavior is needed.
  4. Use Access Modifiers Thoughtfully: Protect internal details with appropriate access modifiers to ensure encapsulation.
  5. Avoid Overriding Unnecessarily: Only override methods when extending or changing behavior purposefully.
  6. Document Your Design: Clearly explain why classes inherit from others for maintainers’ understanding.

Summary

Inheritance is an essential OOP principle that facilitates code reuse, extensibility, and polymorphism in Java programs. By using the extends keyword, you can create subclasses that inherit members of superclasses while optionally customizing behavior through method overriding or adding new features.

We covered:

  • The basics of inheritance syntax.
  • Types like single, multilevel, and hierarchical inheritance.
  • Access modifiers’ impact on inherited members.
  • Constructor chaining using super.
  • Method overriding and polymorphism.
  • Abstract classes enforcing base contract definitions.
  • Restrictions via final classes/methods.
  • Best practices to design maintainable hierarchies.

Mastering inheritance unlocks powerful ways to structure your Java applications efficiently while adhering to OOP principles. Experiment with different types of hierarchies and leverage interfaces alongside inheritance for flexible designs!


By understanding these concepts deeply, you will be well-equipped to implement robust object-oriented solutions using inheritance in Java programming. Happy coding!