JAVA • OBJECT-ORIENTED PROGRAMMING

Polymorphism — Use polymorphism with superclass references (conceptual)

Discover how one variable type can trigger many different behaviors at runtime.

Historical Context & Motivation

Imagine you are building a drawing application. You need to store circles, rectangles, and triangles in a single list and tell each one to draw itself. Without polymorphism, you would have to write separate code paths for every shape type — and every time you invented a new shape, you would have to rewrite that logic. Polymorphism solves this problem by letting you treat many different object types through a single, shared superclass reference.

The word polymorphism comes from the Greek words poly (many) and morph (form). In programming, it means that a single method call can take many forms depending on the actual object behind the reference. This idea did not appear overnight — it evolved across decades of computer science research.

1967
Simula Introduces Classes
Ole-Johan Dahl and Kristen Nygaard create Simula, the first language with classes and inheritance. It plants the seed for polymorphism by allowing subclasses to override methods.
1972
Smalltalk & Message Passing
Alan Kay's Smalltalk language popularizes the idea that objects respond to messages. Different objects can respond to the same message in their own way — pure polymorphism.
1983
C++ Adds Virtual Functions
Bjarne Stroustrup designs C++ with virtual functions, bringing compile-time and runtime polymorphism to a widely used systems language.
1995
Java Makes Polymorphism Default
James Gosling releases Java. Every non-static method is virtual by default, making runtime polymorphism the standard rather than the exception.

So here is the central question this lesson explores: How can a variable declared as a superclass type call the correct overridden method in a subclass — and why is this so powerful?

Core Principles & Definitions

Before diving into code, you need to understand a handful of foundational ideas. Each one builds on the concepts of classes, inheritance, and method overriding that you have already encountered. Together, they form the conceptual backbone of polymorphism through superclass references.

1

Superclass Reference

A variable whose declared type is a parent class (e.g., Animal a). It can point to any subclass object (Dog, Cat, etc.).
2

Actual (Runtime) Type

The actual type is decided by the constructor used with new. In Animal a = new Dog();, the runtime type is Dog.
3

Dynamic Method Dispatch

When you call a method on a superclass reference, Java chooses the overridden version in the actual subclass at runtime — not at compile time.
4

"Is-A" Relationship

Polymorphism relies on the fact that a subclass "is a" kind of its superclass. A Dog is an Animal, so an Animal reference can legally hold a Dog.
5

Compile-Time vs. Runtime View

The compiler sees only the declared type and checks that the method exists there. The JVM sees the actual type and runs the correct version.
✦ KEY TAKEAWAY
Think of a TV remote control. You press the "power" button (one interface), and it turns on whichever TV brand is plugged in — Samsung, LG, Sony. The remote doesn't need to know the brand; each TV implements "power on" its own way. A superclass reference is like that remote: it sends the command, and the actual object decides how to respond.

Visual Explanation — The Polymorphism Pipeline

The diagram below shows how a single superclass reference (Animal a) can point to different subclass objects and how method calls are resolved at runtime through dynamic method dispatch. Follow the arrows from the variable declaration on the left, through the assignment, and down to the method that actually executes.

The top-left box represents the declared type (Animal). The top-right box shows the runtime type assigned via new. The three colored boxes at the bottom represent the subclass-specific implementations the JVM selects at runtime.

Notice that the calling code never mentions Dog, Cat, or Bird directly — it only knows about Animal. This is the beauty of polymorphism: the caller is decoupled from the specific subclass. If you add a Fish class later with its own speak() method, the calling code does not need to change at all.

How It Works — The Compile-Time & Runtime Split

Understanding polymorphism requires separating what happens at two distinct moments: compile time (when the Java compiler checks your code) and runtime (when the Java Virtual Machine actually executes it). These two stages have different jobs and look at different information.

Compile Time — What the Compiler Sees

The compiler only knows the declared type of the variable. When you write Animal a = new Dog();, the compiler sees a as an Animal. If you call a.speak(), the compiler confirms that Animal has a speak() method. If you tried to call a.fetch() — a method defined only in Dog — the compiler would give an error, because Animal does not have fetch().

Runtime — What the JVM Executes

At runtime, the JVM looks at the actual object in memory. Since a really holds a Dog object, the JVM calls Dog's version of speak(). This process is called dynamic method dispatch (or late binding). The binding of the method call to the method body is deferred until the program is running.

⚡ Important Rule
The declared type determines which methods you can call (compile-time check). The actual type determines which version of the method runs (runtime dispatch).

The Three Conditions for Polymorphism

  1. Inheritance — A subclass must extend a superclass (Dog extends Animal).
  2. Method overriding — The subclass must provide its own implementation of a method declared in the superclass.
  3. Superclass reference — The variable holding the object must be declared as the superclass type.

If any one of these three conditions is missing, polymorphism does not occur. For example, if Dog does not override speak(), then the inherited Animal version runs — there is nothing polymorphic about that because only one version exists.

Detailed Breakdown — Class Hierarchy & Method Resolution

Let's examine a complete class hierarchy to see exactly how polymorphism works with superclass references. The diagram below shows the Shape hierarchy — a superclass with three subclasses, each overriding the area() and describe() methods.

The Shape superclass defines area() and describe(). Each subclass overrides area() with its own formula. The dashed box at the bottom shows how Shape references call the correct subclass method.
How the JVM resolves method calls for each superclass reference
CodeDeclared TypeActual TypeMethod That Runs
Shape s = new Circle(5);ShapeCircleCircle's area() → π × 5² ≈ 78.54
Shape s = new Rectangle(4,6);ShapeRectangleRectangle's area() → 4 × 6 = 24.0
Shape s = new Triangle(3,8);ShapeTriangleTriangle's area() → 0.5 × 3 × 8 = 12.0

Worked Example — Tracing Polymorphic Method Calls

Let's trace through a complete example with the Animal hierarchy. Suppose we have these classes: Animal (superclass) with method speak() that returns "..." , Dog (subclass) overriding speak() to return "Woof!", and Cat (subclass) overriding speak() to return "Meow!". Dog also has a unique method fetch().

What Does This Code Print?
1
Step 1 — Identify the DeclarationsWe have two lines of code: Animal a1 = new Dog(); and Animal a2 = new Cat();. Both variables are declared as type Animal (superclass reference), but each holds a different subclass object.
Declared type of a1 and a2: Animal
2
Step 2 — Identify the Actual (Runtime) TypesThe new keyword tells us the actual types. a1 holds a Dog object; a2 holds a Cat object.
Actual type of a1: Dog · Actual type of a2: Cat
3
Step 3 — Trace a1.speak()Compile-time check: Does Animal have speak()? Yes — so the code compiles. Runtime dispatch: The JVM sees that a1 is actually a Dog. Dog overrides speak(), so the Dog version runs.
Output: "Woof!"
4
Step 4 — Trace a2.speak()Same compile-time check passes. At runtime, a2 is actually a Cat. Cat's overridden speak() runs.
Output: "Meow!"
5
Step 5 — What About a1.fetch()?Even though a1 is actually a Dog, the compiler only sees the declared type (Animal). Animal does not have a fetch() method, so this line causes a compile-time error. You would need to cast: ((Dog) a1).fetch();.
Compile error — method not found in declared type
✦ KEY TAKEAWAY
Think of it like a school intercom. The principal (caller) says "Everyone, please announce your club." Every classroom (subclass) responds differently — the chess club says "Chess!", the drama club says "Theater!" The principal used one message, but each classroom responded in its own way. The principal doesn't need to know which club is in which room — that is polymorphism.

Strengths, Limitations & Common Pitfalls

Polymorphism through superclass references is one of the most powerful tools in object-oriented programming, but it comes with trade-offs you should understand. The table below lays out the advantages and the common pitfalls students encounter.

StrengthsLimitations / Pitfalls
Flexibility — One variable type works for many subclasses. You can write methods that accept the superclass and work with any subclass.Loss of subclass-specific methods — A superclass reference cannot call methods unique to the subclass without casting.
Extensibility — Adding a new subclass requires zero changes to existing polymorphic code.Incorrect casting — Casting to the wrong subclass causes a ClassCastException at runtime.
Cleaner code — Eliminates long chains of if/else or switch statements to check object type.Forgetting to override — If a subclass does not override a method, the superclass version runs silently, which can be a subtle bug.
Supports arrays & collections — You can store mixed subclass objects in a single array of the superclass type.Confusion about fields — Polymorphism applies only to methods, not to instance variables. Field access is resolved by declared type.
⚠ Common Mistake
Students often think that polymorphism applies to instance variables the same way it applies to methods. It does not. If both a superclass and subclass define a field with the same name, the version used depends on the declared type, not the actual type. Only methods are dispatched dynamically.
🔑 WHY IT MATTERS
Polymorphism is the reason large Java applications (like Android apps or web servers) can grow without rewriting core logic. Each new feature is a new subclass that plugs into existing code through superclass references — the rest of the system never needs to know the details.

Connection to Advanced Concepts

The superclass-reference style of polymorphism you have learned is often called runtime (or subtype) polymorphism. Java supports other forms of polymorphism as well, and understanding where superclass references fit in the bigger picture will prepare you for more advanced Java courses.

How superclass polymorphism compares to other Java polymorphism features
FeatureSuperclass Reference Polymorphism (This Lesson)Advanced Variations
MechanismMethod overriding + inheritanceInterfaces, abstract classes, generics, method overloading (compile-time polymorphism)
When resolvedRuntime (dynamic dispatch)Overloading resolved at compile time; interfaces use runtime dispatch like superclass references
Reference typeConcrete superclassInterface type, abstract class type, or generic type parameter
Use caseShared behavior among related classes in a hierarchyShared behavior among unrelated classes (interfaces) or type-safe collections (generics)

As you continue studying Java, you will encounter interfaces and abstract classes, which take the same core idea — calling a method on a general reference and having the actual object decide what to do — and make it even more flexible. Interfaces let completely unrelated classes share a common reference type, while abstract classes let you enforce that subclasses must override certain methods. Both rely on the same dynamic dispatch mechanism you learned here.

Practice Problems

PROBLEM 1 — CONCEPTUAL
In the statement Animal a = new Dog();, what is the declared type and what is the actual (runtime) type? Why does this distinction matter for polymorphism?
PROBLEM 2 — BASIC CALCULATION
Given the Shape hierarchy from this lesson, what value is returned by s.area() if Shape s = new Circle(10);? Show your reasoning.
PROBLEM 3 — INTERMEDIATE
Consider this code: Animal[] zoo = { new Dog(), new Cat(), new Dog() }; A loop calls zoo[i].speak() for each element. What output is produced, and why doesn't the code need to check which type each element is?
PROBLEM 4 — APPLIED
You are building a payroll system. The superclass Employee has a method calculatePay(). Subclasses HourlyEmployee and SalariedEmployee override this method. Write pseudocode (or explain in detail) how you would use a superclass reference to process a list of employees without knowing each employee's specific type.
PROBLEM 5 — CRITICAL THINKING
A student writes: Animal a = new Dog(); a.fetch(); where fetch() exists only in Dog. The student argues, "But a IS a Dog, so this should work!" Explain why the compiler rejects this, discuss whether this is a flaw in Java's design, and describe how the student could safely fix the code.

Lesson Summary

Polymorphism allows a single superclass reference to point to any subclass object. The declared type (the superclass) determines which methods the compiler will let you call, while the actual (runtime) type (the subclass) determines which overridden version of a method the JVM executes. This two-phase process is called dynamic method dispatch.

Three conditions must be met for polymorphism to work: inheritance (a subclass extends a superclass), method overriding (the subclass redefines a superclass method), and a superclass reference variable holding the subclass object. Polymorphism makes code flexible, extensible, and clean — you can add new subclasses without modifying existing logic. Remember that polymorphism applies to methods only, not to instance variables, and that subclass-specific methods require casting to access through a superclass reference.

Varsity Tutors • Java • Polymorphism — Use polymorphism with superclass references (conceptual)