foreach-ui logo
codeLanguages
account_treeDSA

Quick Actions

quizlock Random Quiz
trending_uplock Progress
  • 1
  • 2
  • 3
  • 4
  • quiz
Java
  • Understand inheritance and class hierarchies
  • Learn extends and super keywords
  • Master method overriding and polymorphism

Inheritance and Polymorphism

Inheritance and polymorphism are two of OOP's most powerful features. They let you build flexible, maintainable code by creating relationships between classes and treating related objects uniformly.

What is Inheritance?

Inheritance lets one class acquire the properties and methods of another class. Think of it as a family tree of classes.

Real-World Analogy

Consider vehicles:

  • All vehicles have wheels and an engine
  • A Car is a type of Vehicle (with 4 wheels, doors, trunk)
  • A Motorcycle is a type of Vehicle (with 2 wheels, no doors)
  • A Truck is a type of Vehicle (with cargo bed, larger size)

Instead of defining wheels and engine for each type separately, we define them once in Vehicle and have Car, Motorcycle, and Truck inherit from Vehicle.

Basic Inheritance Syntax

// Parent class (Superclass)
public class Vehicle {
    String brand;
    int wheels;
    
    void start() {
        System.out.println("Vehicle is starting...");
    }
    
    void stop() {
        System.out.println("Vehicle is stopping...");
    }
}

// Child class (Subclass)
public class Car extends Vehicle {
    int doors;
    
    void openTrunk() {
        System.out.println("Trunk opened!");
    }
}

The extends keyword establishes the inheritance relationship. Now Car has:

  • Everything from Vehicle (brand, wheels, start(), stop())
  • Its own additions (doors, openTrunk())

Using Inherited Members

public class Main {
    public static void main(String[] args) {
        Car myCar = new Car();
        
        // Inherited from Vehicle
        myCar.brand = "Toyota";
        myCar.wheels = 4;
        myCar.start();  // Inherited method
        
        // Specific to Car
        myCar.doors = 4;
        myCar.openTrunk();  // Car's own method
        
        myCar.stop();  // Inherited method
    }
}

The 'super' Keyword

super refers to the parent class. It's used to:

  1. Call parent class constructors
  2. Access parent class methods
  3. Access parent class variables (when hidden)

Calling Parent Constructors

public class Vehicle {
    String brand;
    int wheels;
    
    public Vehicle(String brand, int wheels) {
        this.brand = brand;
        this.wheels = wheels;
    }
}

public class Car extends Vehicle {
    int doors;
    
    public Car(String brand, int wheels, int doors) {
        super(brand, wheels);  // Call parent constructor
        this.doors = doors;
    }
}

Important: super() must be the first statement in the child constructor!

Accessing Parent Methods

public class Vehicle {
    void start() {
        System.out.println("Vehicle starting...");
    }
}

public class Car extends Vehicle {
    @Override
    void start() {
        super.start();  // Call parent's start() first
        System.out.println("Car engine engaged!");
    }
}

Method Overriding

Method overriding occurs when a child class provides its own implementation of a method that exists in the parent class.

public class Animal {
    void makeSound() {
        System.out.println("Some generic animal sound");
    }
    
    void sleep() {
        System.out.println("Zzz...");
    }
}

public class Dog extends Animal {
    @Override  // This annotation is optional but recommended
    void makeSound() {
        System.out.println("Woof! Woof!");
    }
    // sleep() is inherited without change
}

public class Cat extends Animal {
    @Override
    void makeSound() {
        System.out.println("Meow!");
    }
}

Using the overridden methods:

Dog myDog = new Dog();
myDog.makeSound();  // Outputs: "Woof! Woof!"
myDog.sleep();      // Outputs: "Zzz..." (inherited)

Cat myCat = new Cat();
myCat.makeSound();  // Outputs: "Meow!"

Rules for Method Overriding

  1. Same signature: Method name and parameters must be identical
  2. Same or compatible return type: Can't change to incompatible type
  3. Access modifier: Can't be more restrictive (public → private not allowed)
  4. Use @Override annotation: Helps catch errors at compile time

Polymorphism

Polymorphism means "many forms." It allows us to treat objects of different classes uniformly through a common interface (parent class reference).

The Power of Polymorphism

public class Main {
    public static void main(String[] args) {
        // Parent type can reference child objects
        Animal animal1 = new Dog();
        Animal animal2 = new Cat();
        Animal animal3 = new Animal();
        
        // All called the same way, but behave differently!
        animal1.makeSound();  // "Woof! Woof!"
        animal2.makeSound();  // "Meow!"
        animal3.makeSound();  // "Some generic animal sound"
    }
}

The actual method called is determined at runtime based on the object's actual type, not the reference type. This is called dynamic dispatch or late binding.

Polymorphism with Arrays

public class Main {
    public static void main(String[] args) {
        Animal[] animals = new Animal[3];
        animals[0] = new Dog();
        animals[1] = new Cat();
        animals[2] = new Dog();
        
        // Process all animals uniformly
        for (Animal animal : animals) {
            animal.makeSound();  // Each makes its own sound!
        }
    }
}

Output:

Woof! Woof!
Meow!
Woof! Woof!

Polymorphism with Methods

public class AnimalShelter {
    // This method accepts ANY Animal (polymorphism!)
    void feedAnimal(Animal animal) {
        System.out.println("Feeding the animal...");
        animal.makeSound();  // Different sound depending on actual type
    }
}

public class Main {
    public static void main(String[] args) {
        AnimalShelter shelter = new AnimalShelter();
        
        Dog dog = new Dog();
        Cat cat = new Cat();
        
        shelter.feedAnimal(dog);  // Accepts Dog (is-a Animal)
        shelter.feedAnimal(cat);  // Accepts Cat (is-a Animal)
    }
}

A Complete Example: Employee Hierarchy

// Parent class
public class Employee {
    protected String name;
    protected int id;
    protected double baseSalary;
    
    public Employee(String name, int id, double baseSalary) {
        this.name = name;
        this.id = id;
        this.baseSalary = baseSalary;
    }
    
    public double calculateSalary() {
        return baseSalary;
    }
    
    public void displayInfo() {
        System.out.println("Employee: " + name);
        System.out.println("ID: " + id);
        System.out.println("Salary: $" + calculateSalary());
    }
}

// Child class 1: Manager
public class Manager extends Employee {
    private double bonus;
    
    public Manager(String name, int id, double baseSalary, double bonus) {
        super(name, id, baseSalary);
        this.bonus = bonus;
    }
    
    @Override
    public double calculateSalary() {
        return baseSalary + bonus;
    }
}

// Child class 2: Developer
public class Developer extends Employee {
    private int projectsCompleted;
    private double projectBonus;
    
    public Developer(String name, int id, double baseSalary, double projectBonus) {
        super(name, id, baseSalary);
        this.projectsCompleted = 0;
        this.projectBonus = projectBonus;
    }
    
    public void completeProject() {
        projectsCompleted++;
    }
    
    @Override
    public double calculateSalary() {
        return baseSalary + (projectsCompleted * projectBonus);
    }
}

// Child class 3: Intern
public class Intern extends Employee {
    private int monthsWorked;
    
    public Intern(String name, int id, double hourlyRate) {
        super(name, id, hourlyRate);
        this.monthsWorked = 0;
    }
    
    public void completeMonth() {
        monthsWorked++;
    }
    
    @Override
    public double calculateSalary() {
        return baseSalary * 160 * monthsWorked;  // Hourly rate * hours * months
    }
}

Using the hierarchy polymorphically:

public class Main {
    public static void main(String[] args) {
        // Array of different employee types
        Employee[] employees = new Employee[3];
        
        Manager manager = new Manager("Alice", 101, 80000, 15000);
        employees[0] = manager;
        
        Developer dev = new Developer("Bob", 102, 70000, 2000);
        dev.completeProject();
        dev.completeProject();
        employees[1] = dev;
        
        Intern intern = new Intern("Charlie", 103, 20);
        intern.completeMonth();
        employees[2] = intern;
        
        // Process all employees uniformly!
        System.out.println("=== Company Payroll ===");
        double totalSalary = 0;
        for (Employee emp : employees) {
            emp.displayInfo();  // Polymorphic call
            totalSalary += emp.calculateSalary();  // Polymorphic call
            System.out.println();
        }
        System.out.println("Total Payroll: $" + totalSalary);
    }
}

Protected Access Modifier

The protected access modifier allows child classes to access parent class members:

public class Parent {
    public int publicVar;      // Accessible everywhere
    protected int protectedVar;  // Accessible in subclasses
    private int privateVar;    // NOT accessible in subclasses
    int defaultVar;            // Package-private
}

public class Child extends Parent {
    void display() {
        System.out.println(publicVar);     // OK
        System.out.println(protectedVar);  // OK
        // System.out.println(privateVar); // ERROR!
    }
}

Inheritance vs Composition

Not everything should use inheritance! Sometimes composition (has-a relationship) is better than inheritance (is-a relationship).

Use Inheritance When:

  • There's a clear "is-a" relationship (Dog is-a Animal)
  • The child class is a specialized version of the parent
  • You want to leverage polymorphism

Use Composition When:

  • There's a "has-a" relationship (Car has-a Engine)
  • You want more flexibility
  • You want to avoid tight coupling

Example of Composition:

// Instead of: Car extends Engine (wrong!)
// Use composition:
public class Engine {
    void start() {
        System.out.println("Engine starting...");
    }
}

public class Car {
    private Engine engine;  // Car HAS-A Engine
    
    public Car() {
        this.engine = new Engine();
    }
    
    void start() {
        engine.start();  // Delegate to engine
    }
}

Benefits of Inheritance and Polymorphism

  1. Code Reusability: Write once, use in multiple classes
  2. Maintainability: Changes in parent automatically apply to children
  3. Extensibility: Add new subclasses without modifying existing code
  4. Polymorphism: Write flexible code that works with future classes
  5. Logical Organization: Models real-world relationships

Common Pitfalls

1. Deep Inheritance Hierarchies

// Avoid this:
ClassA → ClassB → ClassC → ClassD → ClassE
// Prefer: Shallow hierarchies (2-3 levels max)

2. Breaking the "is-a" Rule

// Wrong: Circle extends ArrayList (Circle is NOT a list!)
class Circle extends ArrayList { }

// Right: Circle has its own properties
class Circle {
    private double radius;
    private List<Point> points;  // If needed, use composition
}

3. Overusing Inheritance

// Sometimes simple methods are better than inheritance
// Don't create: MathOperations → Addition → AdvancedAddition
// Just use: MathOperations with add() method

Key Takeaways

  • Inheritance allows classes to acquire properties from parent classes using extends
  • super refers to the parent class (constructors, methods, variables)
  • Method overriding lets child classes provide their own implementation
  • Polymorphism allows treating different objects uniformly through a common interface
  • Use @Override annotation for safety and clarity
  • Protected members are accessible in child classes
  • Choose inheritance for "is-a" relationships, composition for "has-a"

What to remember

You now have the core OOP tools: classes, objects, methods, constructors, inheritance, and polymorphism. These aren't just academic concepts—they're how real Java applications are built.

Practice by creating class hierarchies for things you understand: a library system, a game character system, a vehicle fleet. The more you play with these concepts, the more natural they'll feel. And always ask yourself: is this really an "is-a" relationship, or should I use composition instead?

© 2026 forEach. All rights reserved.

Privacy Policy•Terms of Service