Unit 4 · Lesson 5

Inheritance

Every time you write public class Robot extends TimedRobot, you use inheritance. Robot inherits WPILib's entire lifecycle management — the 20ms loop, the mode-switching logic, the Driver Station communication — without writing a line of it. This lesson explains what that inheritance gives you, what it costs, and how to use super and @Override correctly.

By the end of this lesson, you will:

  • Use the extends keyword to create a subclass that inherits a superclass's fields and methods
  • Call superclass constructors with super() and superclass methods with super.method()
  • Override a superclass method with @Override and explain what happens to the original
  • Navigate the WPILib inheritance chain from Object down to your Robot.java
  • Explain when to prefer composition over inheritance for robot subsystems

What Inheritance Gives You

Inheritance lets a class (the subclass) absorb all the fields and methods of another class (the superclass), then add its own or replace existing ones. The subclass is-a kind of the superclass — a Robot is a TimedRobot, an IntakeSubsystem is a SubsystemBase.

The practical value in FRC: WPILib's authors wrote all the hard scheduling infrastructure once. Every team's robot inherits it. You only write the parts that are specific to your robot — the periodic logic, the mechanisms, the autonomous routines.

The WPILib Inheritance Tree

Click any node to see what that class contributes and what it requires the subclass to provide.

WPILib Inheritance Hierarchy — Robot.java
Object
RobotBase
IterativeRobotBase
TimedRobot
Robot (your code)
← click any node to see what it contributes to your Robot.java

The extends Keyword

// Superclass — defines the base behavior public class MotorSubsystem { protected final TalonFX motor; // protected: subclasses can access this protected double currentOutput = 0.0; public MotorSubsystem(int canId) { this.motor = new TalonFX(canId); } public void stop() { motor.set(0.0); currentOutput = 0.0; } public double getOutput() { return currentOutput; } } // Subclass — inherits motor, stop(), getOutput(); adds its own behavior public class IntakeSubsystem extends MotorSubsystem { private final DigitalInput beamBreak; public IntakeSubsystem() { super(Constants.INTAKE_MOTOR_ID); // MUST call super() first beamBreak = new DigitalInput(Constants.BEAM_BREAK_PORT); } // New method — not in superclass public void run() { motor.set(INTAKE_SPEED); currentOutput = INTAKE_SPEED; } public boolean hasGamePiece() { return beamBreak.get(); } }

super — Calling the Parent

super gives you a way to reach up into the superclass from within the subclass. It has two uses:

A subclass constructor must call the superclass constructor — directly or implicitly — before doing anything with the object. If you don't write super(), Java inserts an implicit super() with no arguments. If the superclass has no no-argument constructor, you must call the correct super(…) explicitly.

public IntakeSubsystem() { super(Constants.INTAKE_MOTOR_ID); // calls MotorSubsystem(int canId) // super() must be the very first statement — same rule as this() beamBreak = new DigitalInput(Constants.BEAM_BREAK_PORT); } // In WPILib: SubsystemBase's constructor registers the subsystem // with the Command Scheduler. If you forget super(), the subsystem // is never registered and your commands will never run. public class IntakeSubsystem extends SubsystemBase { public IntakeSubsystem() { // SubsystemBase has a no-arg constructor, so Java calls it implicitly // But being explicit is clearer: super(); motor = new TalonFX(Constants.INTAKE_MOTOR_ID); } }

Inside an overridden method, super.methodName() calls the original superclass implementation. This is useful when you want to extend the parent behavior rather than replace it entirely.

public class IntakeSubsystem extends MotorSubsystem { // Override stop() to also reset state flags — then call parent's stop() @Override public void stop() { gamePieceHeld = false; // reset intake-specific state first super.stop(); // then call MotorSubsystem.stop() which sets motor to 0 } // Without super.stop(), the motor wouldn't stop — just the flag would reset }

@Override — Replacing a Superclass Method

When a subclass defines a method with the same name and parameter list as a superclass method, the subclass version replaces the superclass version for that object. The @Override annotation is technically optional but should always be included — it makes the compiler verify that you're actually overriding something, catching typos before they become runtime surprises.

// In TimedRobot — these methods exist but do nothing by default public void teleopPeriodic() {} public void autonomousInit() {} // In your Robot.java — @Override replaces the empty default implementations @Override public void teleopPeriodic() { intake.update(); // YOUR code runs instead of the empty default drive.update(); } // Without @Override — the compiler doesn't check the signature. // A typo like "teleopPeriodoc()" compiles fine but never runs. public void teleopPeriodoc() { // ❌ typo — silently never called intake.update(); }
🔍 LRI Observation

One of the most reliable ways to produce a robot that does nothing in autonomous is a typo in the method name — autonomousPeroidic instead of autonomousPeriodic. Without @Override, this compiles silently and the method is never called. The fix is one annotation that would have prevented the entire problem. I recommend requiring @Override on every method that overrides a superclass method as a team coding standard enforced by your linter.

Inheritance vs. Composition

Inheritance is the right tool when your class genuinely is-a kind of the parent class. A Robot is a TimedRobot. An IntakeSubsystem is a SubsystemBase. These relationships are stable and the inherited behavior is genuinely useful.

Composition is often better when you want to reuse behavior without the is-a relationship. Instead of inheriting a motor configuration class, your subsystem has-a motor and calls its methods directly. This is what every subsystem already does with hardware objects.

// ❌ Inheritance misused — DriveSubsystem is NOT a TalonFX public class DriveSubsystem extends TalonFX { // Inherits ALL TalonFX methods — 200+ methods exposed to callers // Callers can command the motor directly, bypassing all drive logic } // ✅ Composition — DriveSubsystem HAS TalonFX motors public class DriveSubsystem extends SubsystemBase { private final TalonFX[] driveMotors; // composed, not inherited // Only the methods DriveSubsystem explicitly provides are visible }
💡 The rule: "is-a" = inheritance, "has-a" = composition

An IntakeSubsystem is a SubsystemBase → inherit. An IntakeSubsystem has a TalonFX → compose. When inheritance would expose methods from the parent class that callers shouldn't have access to, or when you're inheriting solely for code reuse rather than for a genuine type relationship, composition is the better design.

⚙️ 🔌 System Check
  • Always use @Override on every overridden method. The compiler catches signature typos. Without it, a misspelled lifecycle method compiles but never runs, producing mysterious "my robot does nothing" bugs.
  • Subsystems extend SubsystemBase, not hardware classes. An IntakeSubsystem extends SubsystemBase is correct. An IntakeSubsystem extends TalonFX is a design error that exposes 200+ motor controller methods to every caller.
  • Call super() in every subclass constructor. For SubsystemBase subclasses, forgetting super() means the subsystem never registers with the Command Scheduler and your commands will fail silently.
  • Don't extend a class just to reuse code. If the subclass isn't genuinely a more specific version of the superclass, use composition instead. Inheritance that's only for code reuse creates fragile coupling.

Knowledge Check

A programmer writes public void teleopPeriodoc() { drive.update(); } without @Override. The robot runs but the drive doesn't move. What happened?
  • 1The method name is spelled correctly and should work
  • 2The method has a typo (Periodoc vs Periodic) — without @Override, Java treats it as a new unrelated method that WPILib never calls; the inherited empty teleopPeriodic() runs instead
  • 3A runtime exception is thrown because the method signature doesn't match
  • 4The method runs but drive.update() throws a NullPointerException
An IntakeSubsystem extends a MotorSubsystem superclass. The superclass constructor requires a CAN ID. What must the first line of IntakeSubsystem's constructor be?
  • 1Initialize the beamBreak field first
  • 2super(Constants.INTAKE_MOTOR_ID); — the superclass constructor must be called first, before any subclass initialization, using the required CAN ID argument
  • 3A this() call to another IntakeSubsystem constructor
  • 4Nothing special — Java calls the superclass constructor automatically after the subclass constructor finishes
Should DriveSubsystem extend TalonFX to reuse TalonFX's methods? Why or why not?
  • 1Yes — inheritance is the correct way to reuse a class's methods
  • 2No — a DriveSubsystem is not a TalonFX (it's not a motor controller); extending TalonFX would expose all 200+ motor controller methods to every caller; the correct design is composition — the subsystem contains TalonFX fields
  • 3Yes, but only if you mark all TalonFX methods as private
  • 4It depends on how many TalonFX methods the subsystem needs
💪 Practice Prompt

Build a Subsystem Hierarchy

  1. Create a MotorSubsystem base class with a protected final TalonFX motor, a parameterized constructor, a stop() method, and a getOutput() getter. This class should work as a general single-motor subsystem.
  2. Create IntakeSubsystem extends MotorSubsystem. Call super() in the constructor. Add a beam break sensor and a run() method. Override stop() to also reset a gamePieceHeld flag, calling super.stop() to ensure the motor actually stops.
  3. Verify @Override catches errors: intentionally misspell stop() in the subclass as stopp() with @Override present. Confirm the compiler rejects it. Then fix the spelling.
  4. Instantiate both classes. Confirm that IntakeSubsystem has access to the inherited getOutput() method without redefining it.
  5. Bonus: Find SubsystemBase in WPILib's source code on GitHub. What does its constructor do? What protected or public methods does it provide to your subsystem? How does this compare to the MotorSubsystem you built?