OOP

Inheritance in Java Tutorial with Code Examples

Inheritance is one of the fundamental concepts of object-oriented programming (OOP) in Java. It allows a new class to acquire the properties and behavior (fields and methods) of an existing class.

The existing class is called the superclass or parent class, while the new class is referred to as the subclass or child class. Inheritance promotes code reusability and hierarchical relationships between classes.

This tutorial will cover how inheritance works in Java, types of inheritance, and key concepts like method overriding, the super keyword, and constructor chaining, with practical examples.

Table of Contents:

1. Introduction to Inheritance

Inheritance allows one class to inherit the properties and behavior of another class. The subclass inherits:

Fields (instance variables).
Methods of the superclass.
This makes inheritance useful for reusing code and creating class hierarchies.

2. Types of Inheritance in Java

Java supports the following types of inheritance:

Single Inheritance: A class inherits from one superclass.
Multilevel Inheritance: A class inherits from a class, which in turn inherits from another class.
Hierarchical Inheritance: Multiple classes inherit from a single superclass.
Note: Java does not support multiple inheritance (where a class inherits from more than one superclass) to avoid ambiguity, but you can achieve similar behavior using interfaces.

3. Defining a Subclass with extends

In Java, inheritance is achieved using the extends keyword. The subclass inherits all non-private fields and methods from the superclass.

Example 1: Simple Inheritance

// Superclass
class Animal {
    String name;

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

// Subclass
class Dog extends Animal {
    public void bark() {
        System.out.println(name + " is barking.");
    }
}

public class Main {
    public static void main(String[] args) {
        // Creating an object of the Dog class
        Dog dog = new Dog();
        dog.name = "Buddy";

        // Calling methods inherited from Animal class
        dog.eat();  // Output: Buddy is eating.

        // Calling method from the Dog class
        dog.bark(); // Output: Buddy is barking.
    }
}

Explanation:

The Dog class inherits the name field and eat() method from the Animal class.
The Dog class adds its own method bark(), demonstrating how subclasses can have their own additional behavior.

4. Method Overriding in Inheritance

Method overriding allows a subclass to provide a specific implementation for a method that is already defined in its superclass. This enables runtime polymorphism.

Example 2: Method Overriding

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

// Subclass
class Dog extends Animal {
    @Override
    public void sound() {
        System.out.println("Dog barks.");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal animal = new Animal();
        animal.sound();  // Output: Animal makes a sound.

        Dog dog = new Dog();
        dog.sound();  // Output: Dog barks.
    }
}

Explanation:

The Dog class overrides the sound() method of the Animal class.
The @Override annotation ensures that we are correctly overriding the method from the superclass.

5. The super Keyword

The super keyword is used in a subclass to refer to its immediate superclass. It can be used to:

Call a superclass method.
Access a superclass field.
Call a superclass constructor.

Example 3: Using super to Call a Superclass Method

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

class Dog extends Animal {
    @Override
    public void sound() {
        super.sound();  // Call the superclass method
        System.out.println("Dog barks.");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.sound();  // Output: Animal makes a sound.
                      //         Dog barks.
    }
}

Explanation:

The super.sound() call in the Dog class invokes the sound() method from the Animal class before executing its own sound() method.

6. Constructor Chaining in Inheritance

When a subclass is instantiated, the constructor of its superclass is invoked first. This process is known as constructor chaining. You can explicitly call the superclass constructor using super().

Example 4: Constructor Chaining

class Animal {
    public Animal() {
        System.out.println("Animal constructor called.");
    }
}

class Dog extends Animal {
    public Dog() {
        super();  // Calls the superclass constructor
        System.out.println("Dog constructor called.");
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();  // Output: Animal constructor called.
                              //         Dog constructor called.
    }
}

Explanation:

When the Dog object is created, the Animal constructor is invoked first, followed by the Dog constructor.
The super() call is optional because Java automatically inserts a call to the superclass constructor if it's not provided.

7. Inheritance and Access Modifiers

Access Levels:
Private members: Not inherited by subclasses.
Protected members: Inherited by subclasses and accessible within the same package.
Public members: Inherited and accessible everywhere.

Example 5: Accessing Protected Members

class Animal {
    protected String name = "Unknown";

    public void display() {
        System.out.println("Animal's name is " + name);
    }
}

class Dog extends Animal {
    public void setName(String name) {
        this.name = name;  // Accessing protected member of the superclass
    }
}

public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.setName("Buddy");
        dog.display();  // Output: Animal's name is Buddy
    }
}

Explanation:

The name field in the Animal class is protected, so it can be accessed by the subclass Dog.

8. Multilevel Inheritance

In multilevel inheritance, a class is derived from another subclass, forming a chain of inheritance.

Example 6: Multilevel Inheritance

class Animal {
    public void eat() {
        System.out.println("Animal is eating.");
    }
}

class Dog extends Animal {
    public void bark() {
        System.out.println("Dog is barking.");
    }
}

class Puppy extends Dog {
    public void weep() {
        System.out.println("Puppy is weeping.");
    }
}

public class Main {
    public static void main(String[] args) {
        Puppy puppy = new Puppy();
        puppy.eat();    // Output: Animal is eating.
        puppy.bark();   // Output: Dog is barking.
        puppy.weep();   // Output: Puppy is weeping.
    }
}

Explanation:

The Puppy class inherits from the Dog class, which in turn inherits from the Animal class. This is an example of multilevel inheritance.

9. Java and Multiple Inheritance (Why It's Not Allowed)

Java does not support multiple inheritance with classes, which means a class cannot inherit from more than one superclass.

This is to avoid the Diamond Problem, where ambiguity arises when a class inherits from two classes that have methods with the same signature.

However, Java allows multiple inheritance through interfaces, meaning a class can implement multiple interfaces.

10. Best Practices for Using Inheritance

Use inheritance for “is-a” relationships: Use inheritance only when the subclass is a specialized version of the superclass. For example, a Dog is an Animal, but a Car is not.

Prefer composition over inheritance: If an “is-a” relationship doesn’t exist, use composition (where one class contains an object of another class) rather than inheritance.

Use final for immutable classes: Mark classes as final if they should not be extended, and methods as final if they should not be overridden.

Minimize use of protected members: Limit the use of protected members to reduce coupling between superclass and subclasses.

11. Conclusion

Inheritance is a key concept in Java that allows for code reuse and a hierarchical relationship between classes.

By using the extends keyword, subclasses can inherit fields and methods from their superclass and override methods to provide specific behavior.

Features such as method overriding, the super keyword, and constructor chaining make inheritance a powerful tool for building flexible, maintainable Java applications.

Understanding how and when to use inheritance, as well as its limitations (such as no multiple inheritance), is crucial for writing clean and efficient object-oriented code in Java.

Related posts

Java Hidden Classes

Java sealed classes

Java Static Classes