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.