Dataslope logoDataslope

Polymorphism

One message, many forms — how a single method call can invoke many different behaviors depending on the actual receiver

The Greek roots of polymorphism literally mean "many shapes." In OOP, the word means: the same message, sent through the same variable type, can produce different behavior at runtime depending on what kind of object is actually receiving it.

This is what makes the lines you wrote on the previous two pages so powerful. When Main loops over a list of Noisy and sends each one sound(), the JVM dispatches to the actual class of each object — not the variable's declared type.

Two-level thinking: compile-time vs. runtime types

Every reference variable in Java has two types associated with it at any moment:

  • Compile-time type (also called static type or declared type): the type written in the variable declaration. The compiler uses it to decide which method names are even allowed.
  • Runtime type (also called dynamic type or actual class): the class of the object the variable currently points to. The JVM uses it to decide which override of a method to actually run.
Code Block
Java 8 (Update 492)

The compiler only allows a.speak() because Animal has a method called speak(). The JVM, at runtime, sees that a actually points to a Dog and runs Dog.speak(). This is sometimes called dynamic dispatch or virtual dispatch, and in Java all instance methods work this way by default.

Why polymorphism is the payoff of OOP

Polymorphism is what makes the previous chapters worth the effort. Encapsulation, inheritance, and interfaces all build up to this moment: a function that processes any number of different concrete types as long as they share the right interface.

That last node — the new shape added next year — is the whole point. You can ship renderAll today, and someone can plug in a brand new Hexagon tomorrow, and your function doesn't change.

Code Block
Java 8 (Update 492)

Renderer.drawAll knows nothing about circles, squares, or triangles. It depends only on Shape. The diversity is real — each shape has its own internal state and computes its own representation — but the interaction with Renderer is uniform.

Subtype polymorphism vs. ad-hoc polymorphism

There are several flavors of polymorphism. In a beginner OOP course the one to know is subtype polymorphism, which is what we just saw: a variable of a parent type can hold any subtype, and calls dispatch to the actual subtype.

You met overloading earlier in Methods and Messages. You will meet generics in passing throughout this course. All three are "polymorphism" in their broad sense, but when designers say "the power of polymorphism" they almost always mean subtype polymorphism.

Upcasting and downcasting

Going from a subtype to a supertype is automatic and always safe — it's called upcasting:

Dog d = new Dog("Rex");
Animal a = d;   // upcast, no syntax needed — every Dog is an Animal

Going the other way — claiming a parent-typed reference is actually a particular subtype — is called downcasting. It requires a cast expression and may fail at runtime if you're wrong.

Code Block
Java 8 (Update 492)

A heavy reliance on instanceof and downcasting is usually a smell — it often means the supertype is missing an abstraction. If you find yourself writing many if (x instanceof A) ... else if (x instanceof B) ... blocks, ask whether the parent type should grow a method that each subtype overrides.

A bigger payoff: open–closed

There is a famous design idea called the open–closed principle: software should be open for extension but closed for modification. Polymorphism is what makes that achievable.

You extend the system by adding new classes that implement the existing interfaces or abstract classes. The existing code, which already worked and was already tested, doesn't have to change.

Practice

Challenge
Java 8 (Update 492)
Polymorphic Employee payroll

Implement a tiny pay system using interface-based polymorphism.

  • Payable interface with a single method int monthlyPayCents().
  • SalariedEmployee implements Payable. Constructor takes monthly salary in cents; monthlyPayCents() returns it directly.
  • HourlyEmployee implements Payable. Constructor takes int hourlyRateCents and int hoursWorked; monthlyPayCents() returns hourlyRateCents * hoursWorked.
  • Payroll has a method int totalCents(List<Payable> payees) that sums the monthly pay of all payees, no matter the concrete class.

The provided Main exercises a small list. Expected output:

total cents: 800000

Test your understanding

QuestionSelect one

Given Animal a = new Dog(); and a.speak();, which method actually runs?

Animal.speak() because a's declared type is Animal

Neither — the compiler rejects this code

Dog.speak() because the runtime type of the object a points to is Dog

It is undefined; the JVM picks at random

QuestionSelect one

Why is polymorphism considered the "payoff" of OOP?

Because it makes programs use less memory

Because it removes the need for any interfaces or abstract classes

Because one piece of generic code can handle any number of concrete types, including types that don't exist yet

Because it allows downcasting without checks

QuestionSelect one

Which of these is a sign you might be underusing polymorphism?

You have classes that implement an interface

You override equals and hashCode on your value classes

Several methods in your code do if (x instanceof A) ... else if (x instanceof B) ... and call different code per branch

Your interfaces have only one method

On this page