Java

Chapter 7: Inheritance and Polymorphism – The True Power of OOP

By Ali Naqi β€’ January 15, 2026

Chapter 7: Inheritance and Polymorphism – The True Power of OOP
🧬 Chapter 7: Inheritance and Polymorphism – The True Power of OOP
1. Inheritance: The "Is-A" Relationship

Inheritance is a mechanism where one class acquires the properties (fields) and behaviors (methods) of another.

Think of it like a family tree. You might inherit your eye color from your parents. In Java, we have:

  • The Superclass (Parent): The class whose features are inherited.

  • The Subclass (Child): The class that inherits those features.

The extends Keyword

In Java, we use the word extends to establish this relationship.


Java

// The Superclass
class Animal {
    String name;
    
    void eat() {
        System.out.println(name + " is eating.");
    }
}

// The Subclass
class Dog extends Animal {
    void bark() {
        System.out.println("Woof! Woof!");
    }
}

Because Dog extends Animal, a Dog object automatically has a name variable and an eat() method, even though we didn't define them inside the Dog class.


Java

Dog myDog = new Dog();
myDog.name = "Buddy";
myDog.eat();  // Inherited from Animal
myDog.bark(); // Defined in Dog

2. The super Keyword: Calling the Parent

What if the parent class has a constructor? As we learned in Chapter 6, constructors aren't inherited like regular methods. However, the child class must call the parent's constructor to properly initialize the inherited fields.

We use the keyword super() to do this.


Java

class Animal {
    String name;

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

class Dog extends Animal {
    String breed;

    Dog(String name, String breed) {
        super(name); // Must be the FIRST line in the child constructor!
        this.breed = breed;
    }
}

3. Method Overriding: Changing the Rules

Sometimes, a child class doesn't want to do things exactly like the parent. For example, all animals makeSound(), but a Cat's sound is different from a Dog's.

Method Overriding allows a subclass to provide a specific implementation of a method that is already provided by its superclass.


Java

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

class Cat extends Animal {
    @Override // This annotation tells Java we are intentionally replacing the parent's method
    void makeSound() {
        System.out.println("Meow!");
    }
}

Overloading vs. Overriding: Don't confuse the two!

  • Overloading (Chapter 4) is same name, different parameters (within the same class).

  • Overriding is same name, same parameters, but in a child class.

4. Polymorphism: "Many Forms"

If Inheritance is the "how," Polymorphism is the "wow." The word literally means "many forms." In Java, it means that a single object can be treated as an instance of its own class, its parent class, its grandparent class, and so on.

Dynamic Method Dispatch

This is the heart of polymorphism. You can store a child object in a parent-type variable.


Java

Animal myPet = new Dog("Rex", "German Shepherd");
myPet.makeSound(); 

Wait, which makeSound() runs? Even though the variable type is Animal, the actual object on the "Heap" (the memory) is a Dog. Java is smart enough to look at the actual object at runtime and say, "Hey, this is a Dog, so I'll use the Dog's bark!"

Why is this useful?

Imagine you have an array of different animals. With polymorphism, you can make them all "perform" without knowing exactly what they are:


Java

Animal[] zoo = { new Dog("Buddy", "Lab"), new Cat("Luna"), new Animal("Generic") };

for (Animal a : zoo) {
    a.makeSound(); // Each animal responds in its own way!
}

If you added a Lion class later, you wouldn't have to change your "for loop" at all. This makes your code extensible.

5. Upcasting and Downcasting

  • Upcasting: Treating a Child as a Parent (e.g., Animal a = new Dog();). This is always safe and happens automatically.

  • Downcasting: Treating a Parent as a Child (e.g., Dog d = (Dog) a;). This is risky. If the animal is actually a Cat and you try to cast it to a Dog, Java will throw a ClassCastException.

To stay safe, always use the instanceof operator:


Java

if (myPet instanceof Dog) {
    Dog d = (Dog) myPet;
    d.bark();
}

6. The final Keyword: Stopping the Chain

Sometimes, you want to put a stop to inheritance.

  1. Final Class: If you declare a class as final, no other class can extend it. (Example: The String class in Java is final).

  2. Final Method: If you declare a method as final, a subclass cannot override it.


Java

public final class SecretBase {
    // No one can "extend" the secret base!
}

7. Abstract Classes: The "Incomplete" Blueprint

Sometimes, a parent class is so generic that it shouldn't ever be an object itself. For example, what does a "Shape" look like? You can't draw a "Shape"; you can only draw a Circle or a Square.

We use the abstract keyword to define classes that cannot be instantiated (you can't say new Shape()).


Java

abstract class Shape {
    abstract void draw(); // No body! The children MUST provide the implementation.
}

8. Practical Example: A Payment System

Let’s apply these concepts to a real-world scenario: a checkout system that handles different payment methods.


Java

abstract class Payment {
    double amount;

    Payment(double amount) {
        this.amount = amount;
    }

    // Every payment must implement this
    abstract void processPayment();
}

class CreditCard extends Payment {
    String cardNumber;

    CreditCard(double amount, String cardNumber) {
        super(amount);
        this.cardNumber = cardNumber;
    }

    @Override
    void processPayment() {
        System.out.println("Processing Credit Card payment of $" + amount + " using card " + cardNumber);
    }
}

class PayPal extends Payment {
    String email;

    PayPal(double amount, String email) {
        super(amount);
        this.email = email;
    }

    @Override
    void processPayment() {
        System.out.println("Processing PayPal payment of $" + amount + " for account: " + email);
    }
}

public class CheckoutSystem {
    public static void main(String[] args) {
        // Polymorphism in action!
        Payment p1 = new CreditCard(150.0, "1234-5678-9012");
        Payment p2 = new PayPal(45.50, "user@example.com");

        process(p1);
        process(p2);
    }

    // This method doesn't care if it's CC or PayPal
    public static void process(Payment p) {
        p.processPayment();
    }
}

πŸ“ Chapter Summary

In this chapter, we unlocked the "Professional" level of Java programming:

  • Inheritance (extends) lets us reuse code and build hierarchies.

  • Method Overriding allows children to redefine parent behaviors.

  • The super keyword gives us access to parent constructors and methods.

  • Polymorphism allows us to write flexible code that works with any subclass of a parent.

  • Abstract Classes act as strict templates for other classes to follow.

You now understand how large-scale Java applications are organized. By grouping common logic in parent classes and specializing in child classes, you can build systems that are incredibly easy to grow and maintain.

But there is one more piece to the OOP puzzle. Classes can inherit from only one parent. What if we want a class to behave like multiple different things?