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
instanceofto 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.
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.
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.
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.
Java 16+ supports pattern matching for instanceof, which combines the check and cast into one line. WPILib's minimum Java version supports this.
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.
- The Command Scheduler relies entirely on polymorphism. Never instantiate commands without assigning them to a
Commandvariable or passing them toschedule()— if a command's declared type is too specific, you lose the benefits of polymorphic scheduling. - Always check
instanceofbefore casting. An incorrect cast throwsClassCastExceptionat 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
instanceofchains, 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
MotorSubsystem variable holds a new IntakeSubsystem(). You call stop(). IntakeSubsystem overrides stop(). Which version runs?List<Command> rather than having separate lists for each command type?MotorSubsystem sub = getActiveSubsystem(); and you need to call hasGamePiece() which only exists on IntakeSubsystem. What is the correct approach?Polymorphic Subsystem Manager
- Using the
MotorSubsystemhierarchy from Lesson 5, create aSubsystemManagerclass with aMotorSubsystem[]field that holds all three subsystem instances. - Add a
stopAll()method that iterates over the array and callsstop()on each element. Verify the correct overridden version fires for each by printing from eachstop()override. - Add a
getMaxOutput()method that iterates and returns the highestgetOutput()value across all subsystems. - Add a method
getIntake()that iterates the array, checksinstanceof IntakeSubsystemwith pattern matching, and returns theIntakeSubsysteminstance (ornullif not found). - Bonus: Look at WPILib's
CommandSchedulersource. Find the code that callsexecute()on running commands. What type are they stored as? How does this match the polymorphism pattern in this lesson?