Unit 4 · Lesson 6

Polymorphism

Polymorphism — "many forms" — is what lets WPILib's Command Scheduler call execute() on any Command object without knowing which specific Command it is. The same method call produces different behavior depending on the runtime type. This is the mechanism that makes extensible frameworks possible.

By the end of this lesson, you will:

  • Distinguish between the declared type and the runtime type of a reference variable
  • Explain how method overriding enables runtime polymorphism
  • Store objects of different subclasses in a superclass-typed variable or array
  • Use instanceof to check runtime type before casting
  • Identify how the Command Scheduler uses polymorphism to run any Command without knowing its concrete type

One Variable, Many Types

In Java, a superclass-typed variable can hold a reference to any subclass object. The declared type is what the variable says it is; the runtime type is what object it actually points to. When you call a method, Java uses the runtime type — the actual object — to decide which implementation runs.

// Declared type: MotorSubsystem // Runtime type: IntakeSubsystem MotorSubsystem sub = new IntakeSubsystem(); // Which stop() runs? The IntakeSubsystem one — Java looks at runtime type sub.stop(); // Superclass array can hold any subclass objects MotorSubsystem[] subsystems = { new IntakeSubsystem(), new ClimberSubsystem(), new ShooterSubsystem() }; // Same call on every element — each runs its own version of stop() for (MotorSubsystem s : subsystems) { s.stop(); // polymorphic dispatch — runs the correct override each time }

Polymorphism in Action: The Subsystem Array

Click "Call stop() on all" to see each subsystem's overridden stop() method fire. Same call, different behavior — depending on the runtime type of each object.

Polymorphism Simulator — MotorSubsystem[]
MotorSubsystem subsystems[0] → IntakeSubsystem waiting…
MotorSubsystem subsystems[1] → ClimberSubsystem waiting…
MotorSubsystem subsystems[2] → ShooterSubsystem waiting…
All declared as MotorSubsystem — runtime types vary. Click to see polymorphic dispatch.

How the Command Scheduler Uses Polymorphism

WPILib's Command Scheduler is the most consequential example of polymorphism in your robot code. The scheduler maintains a list of Command objects — declared type Command, runtime type could be anything: RunIntakeCommand, DriveToPositionCommand, WaitCommand, a parallel group, a sequential group. Every tick, the scheduler calls the same set of methods on every running Command without knowing which specific type it is.

// Simplified Command Scheduler — actual WPILib is more complex List<Command> runningCommands = new ArrayList<>(); // Each tick — 20ms for (Command cmd : runningCommands) { cmd.execute(); // polymorphic: runs RunIntakeCommand.execute() or DriveToPositionCommand.execute() if (cmd.isFinished()) { cmd.end(false); runningCommands.remove(cmd); } } // The scheduler doesn't need to know WHICH command it's running. // Polymorphism means the correct implementation always runs.

Without polymorphism, the scheduler would need a separate code path for every possible command type — an impossible maintenance burden. With it, adding a new command type requires no changes to the scheduler at all.

instanceof and Downcasting

Occasionally you need to recover the specific subtype from a supertype reference — for example, to call a method that only exists on the subclass. The instanceof operator checks the runtime type, and a cast performs the conversion. Always check with instanceof before casting; an incorrect cast throws ClassCastException.

// Check before cast — safe for (MotorSubsystem sub : subsystems) { if (sub instanceof IntakeSubsystem) { IntakeSubsystem intake = (IntakeSubsystem) sub; // safe cast if (intake.hasGamePiece()) { intake.stop(); } } } // ❌ Cast without check — ClassCastException if wrong type IntakeSubsystem intake = (IntakeSubsystem) subsystems[1]; // crashes if [1] is Climber

Java 16+ supports pattern matching for instanceof, which combines the check and cast into one line. WPILib's minimum Java version supports this.

// Pattern matching — check AND bind in one expression for (MotorSubsystem sub : subsystems) { if (sub instanceof IntakeSubsystem intake) { // binds to 'intake' if true if (intake.hasGamePiece()) { intake.stop(); } } } // Cleaner, less error-prone — no separate cast line needed
💡 Frequent instanceof is a design smell

If your code checks instanceof frequently to decide which subtype-specific method to call, that's a signal that the behavior should be pushed into the subclass as an overridden method. The whole point of polymorphism is to eliminate instanceof checks by letting the object decide its own behavior. Use instanceof sparingly — when you genuinely need subtype-specific behavior that can't be expressed through the shared interface.

⚙️ 🔌 System Check
  • The Command Scheduler relies entirely on polymorphism. Never instantiate commands without assigning them to a Command variable or passing them to schedule() — if a command's declared type is too specific, you lose the benefits of polymorphic scheduling.
  • Always check instanceof before casting. An incorrect cast throws ClassCastException at runtime and disables the robot program. The pattern matching syntax (instanceof Type name) eliminates the separate cast line and is always preferred.
  • If you find yourself writing instanceof chains, refactor. Move the differentiated behavior into an overridden method on each subclass. The caller shouldn't need to know which type it's dealing with.

Knowledge Check

A MotorSubsystem variable holds a new IntakeSubsystem(). You call stop(). IntakeSubsystem overrides stop(). Which version runs?
  • 1MotorSubsystem's version — the declared type determines the method called
  • 2IntakeSubsystem's version — Java uses the runtime type (the actual object) to dispatch method calls, not the declared type of the variable
  • 3Both versions run — the superclass version then the subclass version
  • 4A compile error — you cannot call methods through a superclass-typed variable
Why does the Command Scheduler store commands as List<Command> rather than having separate lists for each command type?
  • 1Java generics require a single type parameter
  • 2Memory efficiency — one list uses less RAM than many lists
  • 3Polymorphism — the scheduler calls execute() and isFinished() on every element without knowing its concrete type; each command runs its own implementation automatically; adding new command types requires no changes to the scheduler
  • 4Separate lists would cause thread-safety issues
Your code has MotorSubsystem sub = getActiveSubsystem(); and you need to call hasGamePiece() which only exists on IntakeSubsystem. What is the correct approach?
  • 1Cast directly: ((IntakeSubsystem) sub).hasGamePiece()
  • 2Check first: if (sub instanceof IntakeSubsystem intake) { intake.hasGamePiece(); } — verify the runtime type before calling the subtype-specific method to avoid ClassCastException
  • 3Add hasGamePiece() to MotorSubsystem as a concrete method returning false
  • 4Use reflection to call the method without casting
💪 Practice Prompt

Polymorphic Subsystem Manager

  1. Using the MotorSubsystem hierarchy from Lesson 5, create a SubsystemManager class with a MotorSubsystem[] field that holds all three subsystem instances.
  2. Add a stopAll() method that iterates over the array and calls stop() on each element. Verify the correct overridden version fires for each by printing from each stop() override.
  3. Add a getMaxOutput() method that iterates and returns the highest getOutput() value across all subsystems.
  4. Add a method getIntake() that iterates the array, checks instanceof IntakeSubsystem with pattern matching, and returns the IntakeSubsystem instance (or null if not found).
  5. Bonus: Look at WPILib's CommandScheduler source. Find the code that calls execute() on running commands. What type are they stored as? How does this match the polymorphism pattern in this lesson?