Polymorphism (Java)

Context: FIT2099_MOC · treat many subclasses through one supertype · “one interface, many implementations” · the payoff of inheritance/abstraction Problem it solves: write code against a base type and let each concrete subclass supply its own behaviour at runtime.

Quick Revision

  • 🎯 Trigger: you want one action to behave differently per subclass ➔ call an overridden method through a base-type reference.
  • ⚡ Key Constraint: the runtime object type (not the declared variable type) decides which overridden method runs; but a base-type variable can only see the base type’s methods — subclass-only methods are hidden.

🔧 Minimal Working Example

class Soldier {
    protected int health;
    Soldier(int health) { this.health = health; }
    public int getHealth() { return health; }
    public String print() { return "Soldier (" + getHealth() + " HP)"; }
}
class ArmouredSoldier extends Soldier {
    ArmouredSoldier(int health) { super(health); }
    @Override public int getHealth() { return super.getHealth() * 2; }  // doubled
}
 
Soldier a = new Soldier(50);
Soldier b = new ArmouredSoldier(50);   // upcast: declared Soldier, actual ArmouredSoldier
System.out.println(a.print());          // Soldier (50 HP)
System.out.println(b.print());          // Soldier (100 HP)  <- overridden getHealth() runs

Expected output: Soldier (50 HP) then Soldier (100 HP)print() is inherited unchanged, but it calls the overridden getHealth().

  • UpcastingSoldier b = new ArmouredSoldier(...) — a subclass instance stored in a base-type variable.
  • Dynamic dispatchb.print() calls getHealth(), and the actual object’s override wins at runtime.
  • Needs inheritance ➔ polymorphism only exists where a subclass overrides a supertype method.

🏛️ Structure

classDiagram
    class Soldier {
        #int health
        +getHealth() int
        +print() String
    }
    class ArmouredSoldier {
        +getHealth() int
    }
    class Barrack {
        -Soldier[] soldiers
        +enter(Soldier s) void
    }
    Soldier <|-- ArmouredSoldier
    Barrack o-- Soldier

(Barrack aggregates the base type Soldier only, so any subclass slots in with no edit (↓ coupling, ↑ extensibility); dynamic dispatch runs ArmouredSoldier.getHealth() at runtime.)

🔀 Variations

  • Polymorphic container ➔ store mixed subclasses under the base type:
class Barrack {
    Soldier[] soldiers = new Soldier[10];
    int index = 0;
    void enter(Soldier s) { soldiers[index++] = s; }   // accepts ANY Soldier subtype
}
  • Abstract supertype ➔ the base is often an abstract class: Assessment a = new Test(); — you code to Assessment, each subclass implements mark().

✍️ Practice

⚠️ Common Mistakes

  • 💡 Base variable hides subclass methods ➔ if ArmouredSoldier adds releaseArmour(), a Soldier-typed variable cannot call it — declare it as ArmouredSoldier (or downcast) to reach subclass-only members.
  • 💡 Override, not overload ➔ dynamic dispatch needs the same signature; a different parameter list is overloading and won’t be chosen polymorphically.
  • 💡 Coupling winBarrack depends only on Soldier, so new subclasses slot in with no edit — this is the extensibility payoff of polymorphism.