What is Inheritance in Java?
Inheritance is one of the fundamental principles of object-oriented programming (OOP) in Java. It allows a class (subclass or child class) to inherit properties and behaviors from another class (superclass or parent class). This promotes code reusability, establishes relationships between classes, and supports the "is-a" relationship.
Inheritance enables you to create new classes based on existing classes, inheriting their attributes and methods while allowing you to add new features or modify existing ones. The subclass can use all the public and protected members of the superclass, and can also define its own members.
The main benefits of inheritance include code reusability, method overriding, polymorphism support, and creating hierarchical class structures that model real-world relationships effectively.
"Inheritance allows subclasses to inherit the properties and methods of their parent classes, enabling code reuse and hierarchical relationships."
The extends Keyword
In Java, inheritance is implemented using the extends keyword. The subclass extends the superclass, inheriting all its accessible members:
// Superclass (Parent class)
public class Animal {
protected String name;
protected int age;
public Animal(String name, int age) {
this.name = name;
this.age = age;
}
public void eat() {
System.out.println(name + " is eating");
}
public void sleep() {
System.out.println(name + " is sleeping");
}
}
// Subclass (Child class) - extends the superclass
public class Dog extends Animal {
private String breed;
// Constructor
public Dog(String name, int age, String breed) {
super(name, age); // Call superclass constructor
this.breed = breed;
}
// Additional method specific to Dog
public void bark() {
System.out.println(name + " is barking: Woof!");
}
// Override inherited method
@Override
public void eat() {
System.out.println(name + " the " + breed + " is eating dog food");
}
}
Types of Inheritance in Java
1. Single Inheritance
A subclass inherits from only one superclass. This is the most common and straightforward type of inheritance:
public class Vehicle {
protected String brand;
protected int year;
public void start() {
System.out.println("Vehicle started");
}
}
public class Car extends Vehicle {
private int numberOfDoors;
public void drive() {
System.out.println("Car is driving");
}
}
2. Multilevel Inheritance
A class inherits from another class, which itself inherits from another class, creating a chain of inheritance:
public class Animal {
public void breathe() {
System.out.println("Animal is breathing");
}
}
public class Mammal extends Animal {
public void walk() {
System.out.println("Mammal is walking");
}
}
public class Dog extends Mammal {
public void bark() {
System.out.println("Dog is barking");
}
}
// Usage
Dog dog = new Dog();
dog.breathe(); // Inherited from Animal
dog.walk(); // Inherited from Mammal
dog.bark(); // Own method
3. Hierarchical Inheritance
Multiple subclasses inherit from the same superclass:
public class Shape {
protected String color;
public void setColor(String color) {
this.color = color;
}
public String getColor() {
return color;
}
}
public class Circle extends Shape {
private double radius;
public double getArea() {
return Math.PI * radius * radius;
}
}
public class Rectangle extends Shape {
private double length;
private double width;
public double getArea() {
return length * width;
}
}
public class Triangle extends Shape {
private double base;
private double height;
public double getArea() {
return 0.5 * base * height;
}
}
Method Overriding
Method overriding allows a subclass to provide a specific implementation of a method that is already defined in its superclass. The method in the subclass must have the same signature (name, parameters, and return type) as the method in the superclass:
Rules for Method Overriding
- The method must have the same name as in the superclass
- The method must have the same parameter list
- The return type must be the same or a subtype (covariant return types)
- The access modifier cannot be more restrictive than the superclass method
- The overriding method cannot throw checked exceptions that are not thrown by the superclass method
- Use
@Overrideannotation for clarity and compile-time checking
public class Animal {
public void makeSound() {
System.out.println("Animal makes a sound");
}
public void move() {
System.out.println("Animal moves");
}
}
public class Dog extends Animal {
@Override
public void makeSound() {
System.out.println("Dog barks: Woof!");
}
@Override
public void move() {
System.out.println("Dog runs on four legs");
}
}
public class Cat extends Animal {
@Override
public void makeSound() {
System.out.println("Cat meows: Meow!");
}
@Override
public void move() {
System.out.println("Cat walks gracefully");
}
}
// Usage
Animal dog = new Dog();
Animal cat = new Cat();
dog.makeSound(); // Output: Dog barks: Woof!
dog.move(); // Output: Dog runs on four legs
cat.makeSound(); // Output: Cat meows: Meow!
cat.move(); // Output: Cat walks gracefully
The super Keyword
The super keyword is used to refer to the immediate superclass. It can be used to:
- Call superclass constructors
- Access superclass methods
- Access superclass fields
Calling Superclass Constructors
public class Person {
protected String name;
protected int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}
public class Employee extends Person {
private String employeeId;
private double salary;
// Constructor using super to call parent constructor
public Employee(String name, int age, String employeeId, double salary) {
super(name, age); // Must be the first statement
this.employeeId = employeeId;
this.salary = salary;
}
}
Calling Superclass Methods
public class Vehicle {
public void start() {
System.out.println("Vehicle is starting");
}
}
public class Car extends Vehicle {
@Override
public void start() {
super.start(); // Call parent's start method
System.out.println("Car is starting - checking all systems");
}
}
Accessing Superclass Fields
public class Parent {
protected int value = 10;
}
public class Child extends Parent {
private int value = 20; // This hides the parent's value
public void printValues() {
System.out.println("Child value: " + value); // 20
System.out.println("Parent value: " + super.value); // 10
}
}
Access Modifiers and Inheritance
The accessibility of inherited members depends on their access modifiers:
| Access Modifier | Same Class | Same Package | Subclass (Different Package) | Different Package |
|---|---|---|---|---|
public |
✓ | ✓ | ✓ | ✓ |
protected |
✓ | ✓ | ✓ | ✗ |
default |
✓ | ✓ | ✗ | ✗ |
private |
✓ | ✗ | ✗ | ✗ |
final Keyword with Inheritance
final Classes
A final class cannot be extended (inherited from):
public final class MathUtils {
public static double PI = 3.14159;
public static double calculateArea(double radius) {
return PI * radius * radius;
}
}
// This would cause a compile error
// public class AdvancedMathUtils extends MathUtils { }
final Methods
A final method cannot be overridden in subclasses:
public class Vehicle {
public final void startEngine() {
System.out.println("Engine started");
}
public void stopEngine() {
System.out.println("Engine stopped");
}
}
public class Car extends Vehicle {
// This would cause a compile error
// @Override
// public void startEngine() { }
@Override
public void stopEngine() {
System.out.println("Car engine stopped with safety checks");
}
}
Inheritance and Constructors
When a subclass is instantiated, the superclass constructor is always called first. If the superclass doesn't have a default constructor, the subclass must explicitly call a superclass constructor using super():
public class Parent {
public Parent(int value) {
System.out.println("Parent constructor with value: " + value);
}
}
public class Child extends Parent {
public Child() {
super(10); // Must call parent's constructor explicitly
System.out.println("Child constructor");
}
public Child(int value) {
super(value); // Pass parameter to parent constructor
System.out.println("Child constructor with value");
}
}
Object Class - The Root of All Classes
In Java, all classes implicitly extend the Object class if no explicit superclass is specified. The Object class provides fundamental methods that all Java objects inherit:
public class MyClass {
// Implicitly extends Object
// Same as: public class MyClass extends Object
}
public class MyClass2 extends Object {
// Explicitly extends Object (same as above)
}
// All classes inherit these Object methods:
MyClass obj = new MyClass();
obj.toString(); // Returns string representation
obj.equals(obj2); // Compares objects
obj.hashCode(); // Returns hash code
obj.getClass(); // Returns Class object
obj.clone(); // Creates copy (if Cloneable)
obj.finalize(); // Called before garbage collection
Composition vs Inheritance
Sometimes composition (has-a relationship) is preferred over inheritance (is-a relationship):
Inheritance (is-a relationship)
public class Car extends Vehicle {
// Car IS-A Vehicle
}
Composition (has-a relationship)
public class Car {
private Engine engine; // Car HAS-A Engine
public Car() {
this.engine = new Engine();
}
}
Best Practices for Inheritance
- Follow the "is-a" relationship: Only use inheritance when a subclass truly "is a" type of the superclass
- Prefer composition over inheritance: Use composition when possible to create more flexible designs
- Don't overuse inheritance: Deep inheritance hierarchies can become complex and hard to maintain
- Use protected access wisely: Protected members are visible to subclasses but break encapsulation
- Document inheritance relationships: Clearly document the purpose and contract of inherited methods
- Use final appropriately: Mark classes and methods as final when they shouldn't be extended or overridden
- Override equals() and hashCode() properly: When creating subclasses, ensure proper implementation of these Object methods
- Call super constructors: Always call appropriate superclass constructors in subclass constructors
Common Inheritance Patterns
Template Method Pattern
public abstract class Game {
public final void play() {
initialize();
startPlay();
endPlay();
}
protected abstract void initialize();
protected abstract void startPlay();
protected abstract void endPlay();
}
public class Chess extends Game {
@Override
protected void initialize() {
System.out.println("Setting up chess board");
}
@Override
protected void startPlay() {
System.out.println("Playing chess");
}
@Override
protected void endPlay() {
System.out.println("Chess game finished");
}
}
Inheritance is a powerful feature in Java that enables code reuse and establishes hierarchical relationships between classes. Understanding when and how to use inheritance effectively is crucial for designing maintainable and extensible Java applications. Remember to follow the "is-a" relationship principle and consider composition as an alternative when appropriate.