CodeGym /Courses /JAVA 25 SELF /Polymorphism: what it is and why you need it

Polymorphism: what it is and why you need it

JAVA 25 SELF
Level 18, Lesson 0
Available

1. Introduction

Polymorphism is one of the three cornerstone concepts of object-oriented programming (along with inheritance and encapsulation). Literally, the word comes from Greek “poly” (many) and “morph” (form). In programming it means: one interface — many implementations.

Definition

Polymorphism is the ability of objects of different classes to respond differently to the same messages (method calls).

That is, if you have a makeSound() method, you can call it on any animal, but the cat will meow, the dog will bark, and the cow will moo. For the programmer, it’s just a call to animal.makeSound(), but what actually happens depends on the specific object behind that variable.

Real-world analogy

Imagine you have a remote at home for your TV, and with the same remote you can control speakers, a projector, and even a coffee machine. You press the “Turn on” button — turnOn(), and each device reacts in its own way. The key point is that they all have a “power” button, but the implementation differs.

Example in Java

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

class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("Woof!");
    }
}

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

class Cow extends Animal {
    @Override
    void makeSound() {
        System.out.println("Moo!");
    }
}

Now we can do this:

Animal animal1 = new Dog();
Animal animal2 = new Cat();
Animal animal3 = new Cow();

animal1.makeSound(); // Woof!
animal2.makeSound(); // Meow!
animal3.makeSound(); // Moo!

Note: all variables have the type Animal, but the result of the call depends on the actual type of the object.

2. Types of polymorphism

In Java (and in most OOP languages) there are two main kinds of polymorphism:

Compile-time (static) polymorphism — method overloading (overloading)

This is when a class has several methods with the same name but different parameters. The compiler itself decides which method to call based on the arguments passed.

Example (a quick peek ahead — more details in the next lecture):

class Printer {
    void print(int x) {
        System.out.println("Number: " + x);
    }

    void print(String s) {
        System.out.println("String: " + s);
    }
}

Run-time (dynamic) polymorphism — method overriding (overriding)

This is when a method is defined in a base class and then overridden in subclasses. Which specific method gets called is decided at run time, depending on the actual type of the object.

Example — see the animals above.

3. Why do we need polymorphism?

Polymorphism isn’t just a fancy word for interviews. It’s a tool that makes your code flexible, extensible, and easier to maintain.

Generic code

You can write code that works with objects of a base type without caring about their implementation details. For example, if you have a list of animals, you can iterate over it and call makeSound() on each one without thinking about whether it’s a cat or a dog.

Animal[] animals = { new Dog(), new Cat(), new Cow() };

for (Animal animal : animals) {
    animal.makeSound(); // Each time the "right" method will be invoked
}

Ease of extension

If tomorrow your manager comes and says, “Let’s add a parrot!”, you just write a new class Parrot extends Animal and add it to the array. The rest of the code stays the same. This is open for extension, closed for modification (the OCP principle from SOLID).

Simplifying architecture

You can build complex systems where parts interact with each other through abstractions (base classes or interfaces) without worrying about concrete implementations. This saves time, nerves, and coffee.

4. Key notions: reference type vs. actual type

Reference type of a variable
When you write Animal animal = new Dog();, the variable animal has the reference type Animal, meaning the compiler “thinks” it’s an animal and allows only those methods declared in Animal.

Actual (runtime) type of the object
But in memory the actual object is of type Dog. It determines which method will be called when you invoke makeSound().

Illustration

Animal animal = new Dog();
animal.makeSound(); // Calls Dog.makeSound(), not Animal.makeSound()

Important! Through a base type reference (Animal), you cannot call methods that exist only in Dog unless they are declared in the base class.

Late (dynamic) binding
This is the “magic” that happens at run time: when you call a method through a base-type reference, the JVM looks at the object’s actual type and calls the “right” implementation. That’s polymorphism in action.

5. Practical example: polymorphism in an application

Let’s continue developing our training app. Suppose we’re writing a simple zoo simulation. We have a base class Animal and several subclasses. We want all animals to be able to “make a sound,” but we don’t want to write separate code for each animal type every time.

Step 1: Base class and subclasses

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

class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("Woof!");
    }
}

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

Step 2: Array of animals

Animal[] zoo = { new Dog(), new Cat(), new Animal() };

Step 3: Iteration and method call

for (Animal animal : zoo) {
    animal.makeSound();
}

Program output:

Woof!
Meow!
Some sound...

Note: we don’t know in advance who exactly is in the array — but the program figures it out and calls the appropriate method.

6. Schematic depiction of polymorphism


.        Animal (makeSound)
           /        \
        Dog        Cat
     (makeSound) (makeSound)

Animal animal = new Dog();
animal.makeSound(); // --> Dog.makeSound()

Animal animal = new Cat();
animal.makeSound(); // --> Cat.makeSound()

7. Another example: polymorphism in a real-world task

Suppose you’re writing a program to manage company employees. You have a base class Employee and two subclasses: Manager and Developer. All employees can work — work() — but they do it differently.

class Employee {
    void work() {
        System.out.println("The employee is working.");
    }
}

class Manager extends Employee {
    @Override
    void work() {
        System.out.println("The manager is running a meeting.");
    }
}

class Developer extends Employee {
    @Override
    void work() {
        System.out.println("The developer is writing code.");
    }
}

Now you can do this:

Employee[] staff = { new Manager(), new Developer(), new Employee() };

for (Employee emp : staff) {
    emp.work();
}

Result:

The manager is running a meeting.
The developer is writing code.
The employee is working.

8. When polymorphism does NOT work

Polymorphism works only for methods declared in the base class. If a subclass has its own unique method, you won’t see it through a base-type reference.

class Dog extends Animal {
    void fetchStick() {
        System.out.println("The dog fetches a stick!");
    }
}

Animal animal = new Dog();
// animal.fetchStick(); // Compilation error! That method is not visible via Animal

To call a specific method, you need to cast the variable to the desired type:

if (animal instanceof Dog) {
    ((Dog) animal).fetchStick();
}

But that’s another story — the point is: through polymorphism only methods declared in the base class are available.

9. Common mistakes when working with polymorphism

Error #1: Expecting that a base-type reference exposes all methods of the subclass. In reality, only those declared in the base class are available.

Error #2: Not using the @Override annotation when overriding a method. Without it you might accidentally write a method with an incorrect signature, and then polymorphism won’t work (the base-class method won’t be overridden).

Error #3: Trying to call a subclass-specific method without casting. The compiler won’t allow it because it doesn’t know who exactly is behind the base-type reference.

Error #4: Confusing overloading with overriding. Overloading means multiple methods with the same name and different parameters in one class. Overriding means changing a method’s behavior in a subclass.

1
Task
JAVA 25 SELF, level 18, lesson 0
Locked
Adventures in the Animal World: Polymorphism Lesson!
Adventures in the Animal World: Polymorphism Lesson!
1
Task
JAVA 25 SELF, level 18, lesson 0
Locked
Farm Choir: Polymorphism with Multiple Voices
Farm Choir: Polymorphism with Multiple Voices
1
Task
JAVA 25 SELF, level 18, lesson 0
Locked
Corporate Maze: Polymorphism and the Employee Role
Corporate Maze: Polymorphism and the Employee Role
1
Task
JAVA 25 SELF, level 18, lesson 0
Locked
Fleet Expansion: Polymorphism and New Vehicle Types
Fleet Expansion: Polymorphism and New Vehicle Types
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION