Skip to content
QuizMaker logoQuizMaker
Activity
Java Programming: From Zero to Enterprise
2. Object-Oriented Programming
1. Getting Started with Java & the JVM
2. Data Types & Variables
3. Control Flow: Ifs & Loops
4. String Manipulation in Depth
5. Methods (Functions) Architecture
6. Arrays & The Enhanced For Loop
7. User Input via Scanner
8. Mathematical Operations & The Math Class
9. Operators in Depth
10. Block Scope & Variable Lifecycles
11. Introduction to Object-Oriented Programming
12. Classes & Instances Deep Dive
13. Constructors
14. Encapsulation & The 'this' Keyword
15. Inheritance: Extending Functionality
16. Polymorphism & Method Overriding
17. Abstraction & Abstract Classes
18. Interfaces: The Ultimate Contract
19. Packages & Access Modifiers
20. Enums (Enumerations)
21. Exceptions: Handling Runtime Errors
22. The 'throw' and 'throws' keywords
23. Dates, Times, and Formatting
24. Enumerable Data Structures
25. LinkedLists: The Alternative
26. HashMaps: Key-Value Architecture
27. HashSets: The Art of Uniqueness
28. Iterator: Safe Collection Traversal
29. Wrapper Classes & Autoboxing
30. Basic File I/O
31. Generics: Type-Safe Templates
32. Lambda Expressions & Functional Interfaces
33. The Stream API: Functional Data Pipelines
34. Optional: Beating the NullPointerException
35. Multithreading & Concurrency Basics
36. JDBC: Connecting to SQL Databases
37. Annotations & Reflection
38. The JVM Garbage Collector
39. Introduction to Spring Boot
40. Unit Testing with JUnit
41. Java Collections for DSA
CONTENTS

16. Polymorphism & Method Overriding

The third pillar of OOP. Objects taking on many forms.

Java Programming: From Zero to Enterprise
2. Object-Oriented Programming
February 22, 2026
64
A

[!NOTE] The third pillar of Object-Oriented Programming is Polymorphism, which literally translates to "many forms". It allows us to perform a single overarching action, even if the underlying code is executed differently by different classes.

Method Overriding

In the previous chapter, we saw that a Lion inherits the eat() method from Animal. But what if the generic "The animal consumes food" message isn't accurate enough? What if we want the Lion to eat specifically like a carnivore, without changing the generic Animal file?

We achieve this via Method Overriding. We write the exact same method signature in the Child class, completely overriding the Parent's implementation.

public class Animal {
    public void makeSound() {
        System.out.println("Generic animal noise");
    }
}

public class Dog extends Animal {
    // We add this annotation to tell the compiler to double check that we spelled the method right!
    @Override
    public void makeSound() {
        System.out.println("Woof! Bark!");
    }
}

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

The Magic of Polymorphism

Here is where the architecture becomes incredibly powerful. Because Dog and Cat are both technically Animal objects, Java allows us to declare an Animal variable, but fill it with a completely different child object!

public class Main {
    public static void main(String[] args) {
        // The Box is labeled "Animal", but we put a "Dog" object inside!
        Animal myPet = new Dog();  
        Animal myOtherPet = new Cat();
        
        // Polymorphism in action! Java knows to call the CHILD'S overridden method!
        myPet.makeSound();      // Outputs: "Woof! Bark!"
        myOtherPet.makeSound(); // Outputs: "Meow!"
    }
}

Real-World Use Case

Why is this useful? Imagine building a game engine containing an array of 5,000 active entities. You do not want separate arrays for Enemies, Players, NPCs, and Walls.

Using Polymorphism, you can store everything in a single GameObject[] array.

// One single loop updates thousands of completely different objects!
for(GameObject obj : allEntitiesInGame) {
    // If it's a Player, they move with WASD. If an Enemy, they track you.
    // The overarching loop doesn't care. It just calls the polymorphic method!
    obj.updatePhysics(); 
}

[!TIP] Overloading vs Overriding: Overloading is two methods in the SAME class with DIFFERENT parameters. Overriding is two methods in parent/child classes with the EXACT SAME parameters.

Polymorphism Makes Code Open to New Types

The big benefit of polymorphism is that old code can work with new child classes. If a payment system depends on a parent type or interface, you can add a new payment method later without rewriting every caller.

One List, Many Behaviors

class Notification {
    void send(String message) {
        System.out.println(message);
    }
}

class EmailNotification extends Notification {
    @Override
    void send(String message) {
        System.out.println("Email: " + message);
    }
}

class SmsNotification extends Notification {
    @Override
    void send(String message) {
        System.out.println("SMS: " + message);
    }
}
Notification[] notifications = {
    new EmailNotification(),
    new SmsNotification()
};

for (Notification notification : notifications) {
    notification.send("Your order is ready");
}

Dynamic Dispatch

Java chooses the overridden method based on the actual object at runtime, not only the reference type. That is why a Notification reference can still call the EmailNotification version of send().

Common Mistakes

  • Forgetting @Override, which makes typos harder to catch.
  • Confusing overloading with overriding.
  • Expecting fields to behave polymorphically like methods. Field access is not overridden the same way methods are.
  • Downcasting too often instead of designing a better parent method or interface.

Mini Practice

Create a parent class Shape with area(). Add Circle and Rectangle. Store both in a Shape[] and print their areas in one loop.

Practice Lab: Shapes With One Loop

Use one parent reference type to call different child behavior.

  1. Create a parent class Shape with method area().
  2. Create Circle, Rectangle, and Square.
  3. Override area() in each child.
  4. Store all shapes in a Shape[].
  5. Loop once and print every area.

Goal: See dynamic dispatch in action through overridden methods.

Revision Checkpoint

  • Polymorphism: One parent/reference type can point to many child object types.
  • Override: Child provides its own version of a parent method.
  • @Override: Helps the compiler catch mistakes.
  • Dynamic dispatch: Runtime object decides which overridden method runs.
  • Overload vs override: Different parameters vs child replacement.

Before the quiz: Explain why Animal a = new Dog() can call the dog sound.

Share this article

Share on TwitterShare on LinkedInShare on FacebookShare on WhatsAppShare on Email

Test your knowledge

Take a quick quiz based on this chapter.

mediumJava
Quiz: Polymorphism
10 questions5 min
Lesson 6 of 10 in 2. Object-Oriented Programming
Previous in 2. Object-Oriented Programming
15. Inheritance: Extending Functionality
Next in 2. Object-Oriented Programming
17. Abstraction & Abstract Classes
Back to Java Programming: From Zero to Enterprise
Back to moduleCategories