Inheritance in Java: Complete Guide with Examples

0

Inheritance is one of the fundamental principles of Object-Oriented Programming in Java. It allows one class to acquire accessible fields and methods from another class, creating a relationship between a more general type and a more specialized type.

The idea is simple: if a Vehicle has common behavior such as starting and stopping, a Car can reuse that behavior instead of implementing the same logic again. The child class can then add behavior specific to cars.

Why Do We Need Inheritance?

Imagine an application that manages different types of employees. Many employees may share common information such as name, ID, and department, while specialized employees may have additional responsibilities.

class Employee {

    String name;
    int id;

    void work() {
        System.out.println("Employee is working");
    }
}

A developer could copy this code into every specialized employee class, but duplication quickly becomes difficult to maintain. Inheritance allows common functionality to be defined once and reused where appropriate.

Remember: Inheritance represents an “is-a” relationship. A Car is a Vehicle; a Dog is an Animal.

Basic Inheritance Syntax

Java uses the extends keyword to establish class inheritance.

class Vehicle {

    void start() {
        System.out.println("Vehicle starts");
    }
}

class Car extends Vehicle {

    void drive() {
        System.out.println("Car is driving");
    }
}

Here, Vehicle is the parent class, also called the superclass or base class. Car is the child class, also called the subclass or derived class.

Because Car extends Vehicle, a Car object can use the accessible start() method inherited from Vehicle.

Using an Inherited Method

public class Main {

    public static void main(String[] args) {

        Car car = new Car();

        car.start();
        car.drive();
    }
}

The Car class does not declare start(), but it can use the inherited method because it extends Vehicle.

Parent Class and Child Class

Term Meaning
Superclass The class whose accessible members are inherited
Subclass The class that extends another class
Parent class Another common name for superclass
Child class Another common name for subclass
extends Keyword used for class inheritance

Adding New Behavior in the Child Class

Inheritance does not restrict the child class to inherited functionality. A subclass can introduce its own fields and methods.

class Vehicle {

    void start() {
        System.out.println("Vehicle starts");
    }
}

class Car extends Vehicle {

    void openTrunk() {
        System.out.println("Trunk opened");
    }
}

A Car object can now use both the inherited start() method and its own openTrunk() method.

Inherited Fields

A subclass can also use accessible fields inherited from its superclass.

class Person {

    String name;
}

class Student extends Person {

    void display() {
        System.out.println(name);
    }
}

The Student class can access name because the field has package-private access and the example classes are in the same package.

Private Members and Inheritance

A private member belongs to the superclass but cannot be directly accessed by the subclass.

class Person {

    private String name;

    public String getName() {
        return name;
    }
}

class Student extends Person {

    void display() {
        System.out.println(getName());
    }
}

The subclass cannot directly write name because it is private. However, it can use the public method provided by the parent class.

Important: Private members are not directly accessible from a subclass. If the parent class needs to expose functionality to subclasses or callers, it should provide an appropriate accessible method or member.

Protected Members and Inheritance

The protected modifier is often used when a superclass intentionally allows subclasses to access a member.

class Vehicle {

    protected int speed;
}

class Car extends Vehicle {

    void accelerate() {
        speed = 100;
    }
}

The subclass can access the protected member because it inherits from Vehicle.

Method Overriding

A subclass can provide its own implementation of an inherited instance method. This is called method overriding.

class Animal {

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

class Dog extends Animal {

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

The @Override annotation tells the compiler and other developers that the method is intended to override a superclass method.

Why Use @Override?

The @Override annotation is more than documentation. It allows the compiler to detect many mistakes, such as accidentally changing the method signature instead of actually overriding the parent method.

class Dog extends Animal {

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

Using @Override is a strong professional practice whenever you intentionally override a method.

Calling the Parent Method with super

Sometimes a subclass wants to add behavior while still using the superclass implementation. Java provides the super keyword for this purpose.

class Animal {

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

class Dog extends Animal {

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

The call to super.makeSound() invokes the superclass version of the method before the subclass adds its own behavior.

Calling a Parent Constructor with super()

The super() syntax can also be used to invoke a superclass constructor.

class Person {

    String name;

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

class Student extends Person {

    int rollNumber;

    Student(String name, int rollNumber) {
        super(name);
        this.rollNumber = rollNumber;
    }
}

The superclass constructor initializes the inherited portion of the object, while the subclass constructor initializes its own state.

Constructor Chaining in Inheritance

When an object of a subclass is created, constructor execution involves the superclass portion before the subclass constructor completes.

class Parent {

    Parent() {
        System.out.println("Parent constructor");
    }
}

class Child extends Parent {

    Child() {
        System.out.println("Child constructor");
    }
}

public class Main {

    public static void main(String[] args) {
        Child child = new Child();
    }
}

The output is:

Parent constructor
Child constructor

This ordering ensures that the inherited portion of the object is initialized before the subclass-specific initialization proceeds.

Implicit super()

If a subclass constructor does not explicitly call a superclass constructor, Java attempts to insert a call to the superclass's no-argument constructor.

class Parent {

    Parent() {
        System.out.println("Parent");
    }
}

class Child extends Parent {

    Child() {
        System.out.println("Child");
    }
}

Conceptually, the subclass constructor behaves as though super() were placed at the beginning.

Important: If the superclass does not provide an accessible no-argument constructor, the subclass must explicitly invoke an appropriate superclass constructor using super(arguments).

Types of Inheritance Supported by Classes

Java class inheritance can form several common structures.

Type Structure
Single inheritance One child extends one parent
Multilevel inheritance A class extends a class that itself extends another class
Hierarchical inheritance Multiple child classes extend the same parent

Single Inheritance

class Animal {
}

class Dog extends Animal {
}

Here, one subclass directly extends one superclass.

Multilevel Inheritance

class Animal {
}

class Mammal extends Animal {
}

class Dog extends Mammal {
}

The inheritance chain contains multiple levels. A Dog is a Mammal, and a Mammal is an Animal.

Hierarchical Inheritance

class Animal {
}

class Dog extends Animal {
}

class Cat extends Animal {
}

Here, multiple subclasses share the same parent class.

Does Java Support Multiple Inheritance with Classes?

Java does not allow a class to directly extend multiple classes.

class A {
}

class B {
}

class C extends A, B {
}

The code above is invalid Java syntax.

Java uses interfaces to provide a different mechanism for supporting multiple types of behavior without allowing the ambiguity associated with multiple class inheritance.

Why Java Avoids Multiple Class Inheritance

Suppose two parent classes provide methods with the same signature. If a child inherited both implementations, the language would need additional rules to determine which implementation should win.

class A {

    void display() {
        System.out.println("A");
    }
}

class B {

    void display() {
        System.out.println("B");
    }
}

Allowing a class to extend both could create ambiguity around display(). Java avoids this class-level multiple inheritance model.

Inheritance and Polymorphism

Inheritance creates the relationships that make runtime polymorphism possible.

class Animal {

    void makeSound() {
        System.out.println("Animal sound");
    }
}

class Dog extends Animal {

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

public class Main {

    public static void main(String[] args) {

        Animal animal = new Dog();

        animal.makeSound();
    }
}

Although the reference type is Animal, the actual object is a Dog. The overridden Dog implementation executes at runtime.

This powerful combination of inheritance and method overriding is explored more deeply in the polymorphism chapter.

IS-A Relationship

Inheritance should normally represent a meaningful “is-a” relationship.

Relationship Inheritance Appropriate?
Dog is an Animal Yes
Car is a Vehicle Yes
Manager is an Employee Potentially yes
Engine is a Car No
Car has an Engine Composition is more appropriate

Inheritance vs Composition

One of the most important design decisions is knowing when not to use inheritance. If the relationship is “has-a” rather than “is-a”, composition is often a better choice.

class Engine {

    void start() {
        System.out.println("Engine starts");
    }
}

class Car {

    private Engine engine = new Engine();

    void startCar() {
        engine.start();
    }
}

A car has an engine. It is not an engine. Therefore, composition models this relationship more naturally.

Method Inheritance and Overriding Rules

A subclass cannot override every method it inherits. Java places specific restrictions on overriding.

  • A private method is not overridden because it is not accessible to the subclass.
  • A final instance method cannot be overridden.
  • A static method is hidden rather than overridden.
  • An overriding method cannot reduce the visibility of the inherited method.
  • The overriding method must have a compatible return type according to Java's overriding rules.

final Class and Inheritance

A class declared with final cannot be extended.

final class SecurityManager {
}

class CustomManager extends SecurityManager {
}

The subclass declaration is invalid because a final class cannot have subclasses.

Making a class final is useful when its design should not permit inheritance.

final Method and Inheritance

A final method can be inherited, but a subclass cannot override it.

class Account {

    final void validate() {
        System.out.println("Validation");
    }
}

class SavingsAccount extends Account {

    void validate() {
        System.out.println("Custom validation");
    }
}

The subclass cannot declare an overriding version of validate() because the parent method is final.

Object: The Root of Java's Class Hierarchy

Every Java class ultimately derives from Object, directly or indirectly, except that primitive types are not classes and therefore are not part of this class hierarchy.

class Animal {
}

class Dog extends Animal {
}

The conceptual inheritance chain is:

Dog
 ↓
Animal
 ↓
Object

This is why methods such as toString(), equals(), and hashCode() are available to ordinary Java objects through the Object class.

Common Beginner Mistakes

  • Using inheritance simply to reuse code without checking whether an “is-a” relationship actually exists.
  • Assuming private members can be directly accessed by subclasses.
  • Forgetting to use @Override when overriding methods.
  • Trying to extend more than one class.
  • Confusing method overriding with method overloading.
  • Assuming static methods participate in runtime method overriding.
  • Ignoring the effect of final classes and methods on inheritance.

Best Practices

  • Use inheritance when the child genuinely represents a specialized form of the parent.
  • Prefer composition when the relationship is naturally “has-a”.
  • Use @Override whenever overriding an inherited method.
  • Keep inheritance hierarchies understandable and reasonably shallow.
  • Avoid inheritance solely for convenient code reuse.
  • Design parent classes carefully because subclasses can become dependent on their behavior and contracts.

Interview Insights

A common interview question is: “What is inheritance in Java?”

A strong answer is: Inheritance is a mechanism through which a subclass derives accessible behavior and state from a superclass using the extends keyword. It supports reuse, specialization, and polymorphic relationships.

Another common question is: “Does Java support multiple inheritance?” Java does not support multiple inheritance of classes, meaning a class cannot extend more than one class. Java does, however, support implementing multiple interfaces.

Interviewers also often ask about the difference between inheritance and composition. A practical answer is that inheritance models an “is-a” relationship, while composition models a “has-a” relationship and often provides looser coupling.

Quick Learning Check

Before moving to polymorphism, make sure you can answer these questions:

  • What is inheritance and which keyword is used to create it?
  • What is the difference between a superclass and a subclass?
  • Why can't a subclass directly access private members?
  • What is method overriding?
  • What is the purpose of super?
  • Why does Java not support multiple inheritance of classes?
  • When is composition preferable to inheritance?

Final Takeaway

Inheritance allows Java classes to build specialized types from existing types, reducing unnecessary duplication and establishing meaningful relationships between objects. The extends keyword creates class inheritance, super provides access to superclass functionality and constructors, and method overriding allows subclasses to provide specialized behavior. The most important design lesson is to use inheritance because the relationship is genuinely meaningful—not merely because sharing code appears convenient.

Post a Comment

0Comments
Post a Comment (0)