dev only

Polymorphism

Many Forms, One Piece of Code

Polymorphism ("many forms") means the method that actually runs at runtime depends on the object's actual type — not the type its variable was declared with. This is the concrete payoff of everything Ch.17 and Lessons 18.1-18.2 built toward: real WPILib code treats a TalonFX or a SparkMax as just a MotorController, and calls the same method on either without caring which one it actually is:

public void driveForward(MotorController controller)
{
    controller.set(0.5); // works identically whether controller is a TalonFX or a SparkMax
}

Declared Type vs. Actual Type

Every variable has two types that matter: its declared (compile-time) type — what it's written as in the code — and its actual (runtime) type — the real class that was actually constructed with new:

Subsystem s = new Intake("Front Intake");
// declared type: Subsystem
// actual type:   Intake

The compiler only ever checks the declared type when deciding whether code compiles — it verifies that whatever method you're calling actually exists on that declared type (or an ancestor of it):

Object message = new String("hi");
message.indexOf("h"); // COMPILE ERROR — Object has no indexOf method, even though String does

Even though the real object here is a String (which absolutely does have indexOf), the compiler only looks at message's declared type, Object — and refuses to compile, since Object never promises an indexOf method.

Runtime Dispatch: The Actual Type Decides Which Version Runs

Once code does compile, the JVM decides which overridden version of a method actually executes by looking at the object's real, actual type — starting there and searching upward through its superclasses if needed (same lookup process as Lesson 18.2's super):

Subsystem s = new Intake("Front Intake"); // declared Subsystem, actually an Intake
s.stop(); // the compiler only confirms Subsystem HAS a stop() method — but Intake's own override is what actually runs

This is exactly what makes the earlier Subsystem[]/ArrayList<Subsystem> examples (Lesson 17.3) genuinely useful — a single loop calling s.stop() on each element automatically runs whichever subclass's own stop() actually applies, without an if/else chain checking each one's specific type:

for (Subsystem s : allSubsystems)
{
    s.stop(); // Intake's stop(), Climber's stop(), Shooter's stop() — whichever is actually correct for each object
}

Static Methods Don't Get This

Polymorphic dispatch only applies to regular (instance) methods. A static method call is resolved entirely by the declared type, at compile time — there's no runtime lookup involved at all, since static methods belong to the class itself, not to any particular object.

Common Pitfalls

  • Assuming the compiler checks the actual type. It never does — message.indexOf("h") fails to compile even when the real object genuinely has that method, because the compiler only ever consults the declared type.
  • Forgetting that runtime dispatch is what actually decides which override runs. The declared type only gates whether the code compiles — which specific version executes is always decided by the real, actual object type.
  • Expecting polymorphism to apply to static methods. Static method calls are resolved by declared type alone, with no runtime dispatch — they don't participate in overriding the way instance methods do.

Key Takeaways

  • Polymorphism means the method that runs depends on an object's actual runtime type, not its variable's declared type.
  • The compiler only checks the declared type for whether a method call is legal; the JVM decides which overridden version actually runs based on the real, actual type.
  • This is why a Subsystem-typed array or method parameter can transparently work with Intake, Climber, Shooter, or any other subclass — one piece of code, many possible real behaviors.
  • Static methods don't participate in this — they're resolved entirely by declared type, at compile time.

Derived from csawesome-2026: unit-5-inheritance-optional/5.06-polymorphism.md (CSA 5.6) Deck context: mechacoder-test/src/lessons/java-2.js, slide 5 ("Polymorphism: Many Forms") — TalonFX/SparkMax-as-MotorController example