CodeGym /Courses /JAVA 25 SELF /Using polymorphism in practice

Using polymorphism in practice

JAVA 25 SELF
Level 18, Lesson 3
Available

1. Polymorphism in collections: why do you need it?

Let’s start with a question: “Why do we even need polymorphism in real applications?”

Imagine a zoo. You have a base class Animal and a bunch of subclasses: Dog, Cat, Cow, Parrot, and even Platypus (a platypus, for fans of exotic animals). Each of them can make a sound (makeSound()), but each does it in its own way.

Instead of creating separate arrays for each animal, you declare an array or list of type Animal and put anyone there:

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

Now you can iterate over this array and call makeSound() for each:

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

Magic! Each object knows what sound to make, and you don’t need to write a bunch of ifs or switches.

Analogy

It’s like giving the command “Speak!” to a group of animals, and each of them decides what to do: the dog barks, the cat meows, and the cow moos. You don’t clarify who is who—you just call the same method.

2. Practical example: employee hierarchy

Let’s make an example closer to real life (and to your future job in IT). Imagine a company with different employees: managers, developers, testers. Each has a work() method, but it’s implemented differently.

Declaring the base class

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

Subclasses

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

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

public class Tester extends Employee {
    @Override
    public void work() {
        System.out.println("The tester is looking for bugs.");
    }
}

Using an array/list of the base type

public class CompanyDemo {
    public static void main(String[] args) {
        Employee[] team = {
            new Manager(),
            new Developer(),
            new Tester(),
            new Developer()
        };

        for (Employee e : team) {
            e.work(); // The "right" version of the method will be invoked for each object
        }
    }
}

Program output:

The manager is holding a meeting.
The developer is writing code.
The tester is looking for bugs.
The developer is writing code.

What are the advantages?

  • You don’t write lots of checks like “If this is a Developer—do such-and-such.”
  • To add a new employee (for example, a Designer)—just create a new class and add it to the array.
  • The code that uses the employee array doesn’t change at all!

3. Benefits of polymorphism: flexibility and extensibility

Let’s say your company now has a new type of employee—a Designer. All you need to do is create a new class:

public class Designer extends Employee {
    @Override
    public void work() {
        System.out.println("The designer is creating mockups.");
    }
}

Now you can add the designer to the team:

Employee[] team = {
    new Manager(),
    new Developer(),
    new Tester(),
    new Designer()
};

Voilà! The program immediately works correctly with the new type of employee without changing a single line in the code that iterates over the array and calls work().

This is extensibility: your code easily adapts to new types of objects.

4. Limitations of polymorphism: the flip side

Unfortunately, any “magic” has its limitations (and a cost, like in any RPG).

Only base-class methods are available

When you work with a variable of type Employee, you can call only the methods declared in Employee. If Developer has a special method writeCode(), you cannot call it directly:

Employee e = new Developer();
// e.writeCode(); // Compilation error: there is no such method in Employee!

If you really want to call a specific method, you’ll have to do a cast. But that’s a last resort. If you find yourself casting frequently, consider revisiting your design—the base class or interface should contain the necessary method.

if (e instanceof Developer) {
    Developer dev = (Developer) e;
    dev.writeCode();
}

But then you lose the universality and elegance you were aiming for. So try to design the base class so that it includes only those methods that are truly needed by all subclasses.

5. Practice: implement the employee hierarchy

Let’s do something both fun and useful: we’ll write a simple application with several types of employees and use polymorphism to process them.

Step 1: Base class and subclasses

// Employee.java
public class Employee {
    public void work() {
        System.out.println("Employee is working...");
    }
}

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

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

// Tester.java
public class Tester extends Employee {
    @Override
    public void work() {
        System.out.println("The tester is looking for bugs.");
    }
}

Step 2: Main class

// CompanyDemo.java
public class CompanyDemo {
    public static void main(String[] args) {
        Employee[] team = {
            new Manager(),
            new Developer(),
            new Tester(),
            new Developer()
        };

        for (Employee e : team) {
            e.work();
        }
    }
}

Step 3: Add extensibility

Suppose that a month later a new employee joins—the designer. All you need is:

public class Designer extends Employee {
    @Override
    public void work() {
        System.out.println("The designer is creating mockups.");
    }
}

That’s it—now you can add the designer to the team:

Employee[] team = {
    new Manager(),
    new Developer(),
    new Tester(),
    new Designer()
};

Result

The main code (CompanyDemo) remained unchanged! That’s the power of polymorphism.

6. Common mistakes when using polymorphism

Mistake #1: Expecting access to subtype-specific methods via a base-type reference.
Very often, beginners try to call subtype-specific methods through a variable of the superclass type. For example:

Employee e = new Developer();
// e.writeCode(); // Error! This method is not defined in Employee.

To call a specific method, you need to cast—but then you lose generality.

Mistake #2: Not using the @Override annotation.
If you forget the annotation, you might accidentally write a new method instead of overriding (for example, by misspelling the name). Then polymorphism simply won’t work, and the superclass version will be called.

Mistake #3: No common interface.
If the base class doesn’t contain the necessary method, polymorphism is impossible. For example, if Employee has no work() method, a loop over the employee array can’t call this method for everyone.

Mistake #4: Violating the open/closed principle.
If adding a new type of employee requires changing the code that iterates over the array/list, you’re not using polymorphism properly.

1
Task
JAVA 25 SELF, level 18, lesson 3
Locked
Voices of the Digital Animal Encyclopedia
Voices of the Digital Animal Encyclopedia
1
Task
JAVA 25 SELF, level 18, lesson 3
Locked
Corporate Symphony: Polymorphism in Employee Work
Corporate Symphony: Polymorphism in Employee Work
1
Task
JAVA 25 SELF, level 18, lesson 3
Locked
Team extension: Adding a new employee type
Team extension: Adding a new employee type
1
Task
JAVA 25 SELF, level 18, lesson 3
Locked
Dynamic Office: Extensibility and the Versatility of Polymorphism
Dynamic Office: Extensibility and the Versatility of Polymorphism
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION