sign in
Start typing to search across all 16 weeks…
home / summer / week 6
Summer · Week 6 of 8

Inheritance & Polymorphism

building class hierarchies, reusing code, and the OOP stuff that makes WPILib make sense :D

Why this matters: every single time you write extends SubsystemBase or extends Command in WPILib, you are using inheritance. these aren't just keywords you copy-paste — they're the reason the whole command-based framework works. this week makes all of that make sense.

Inheritance

ok so last week we learned that a class is a blueprint. this week we're asking: what if you could take an existing blueprint and build on top of it? keep everything that's already there, and just add your own stuff? no copy-pasting, no duplicating, no re-writing the same methods five times.

that's inheritance. and once you understand it, every WPILib subsystem you've ever seen will suddenly make total sense.

the inheritance mental model

think about a family tree. a kid inherits traits from their parents — eye color, height, maybe a stubborn streak. but they also have their own unique traits on top. the kid IS a person. the parent IS a person. but the kid is also their own specific individual with extra stuff the parent doesn't have.

in programming, inheritance is the same idea. you have a parent class (also called a superclass or base class) that has some fields and methods. you have a child class (also called a subclass) that automatically gets all of those fields and methods for free, and can add its own on top. the child class IS-A type of the parent class.

the whole point is code reuse. instead of writing the same getName() method in Dog, Cat, and Bird, you write it ONCE in Animal, and all three get it automatically. you fix a bug in Animal, it's fixed everywhere. you add a feature to Animal, every child class gets it for free. this is one of the big wins of object-oriented programming.

your entire robot codebase is built on inheritance. every subsystem you write extends SubsystemBase. every command you write extends Command. WPILib wrote those parent classes once, with all the framework plumbing built in, and you just build on top. without inheritance, you'd have to re-implement all of that plumbing from scratch every time you make a new subsystem. that would be awful.

the vehicle analogy

let's make this really concrete. imagine you have a general Vehicle class. it has fields like speed, color, and numberOfWheels. it has methods like accelerate() and brake(). any vehicle can do those things.

now you want to make a Car. a Car IS-A Vehicle. it has all that Vehicle stuff. but it also has a trunkSpace field and an openTrunk() method that trucks don't have. a Truck is also a Vehicle, but it has a cargoCapacity field and a lowerTailgate() method instead.

you don't want to copy-paste all the Vehicle code into Car and Truck. that's a nightmare. if you fix a bug in brake(), you'd have to fix it in three places. if you add a new feature to Vehicle, you'd have to add it to every subclass by hand. instead, Car and Truck extend Vehicle. they inherit everything, and each adds their own specific stuff on top.

FRC version of this analogy: SubsystemBase is the Vehicle. DriveSubsystem, ShooterSubsystem, IntakeSubsystem — those are all the Car and Truck. they all inherit from SubsystemBase, which means they all automatically get the periodic() method hook, they all register with the CommandScheduler, they all behave like a proper subsystem. each one just adds its own motors and logic on top. SubsystemBase was written once by the WPILib team; your team just builds on it.

the extends keyword — syntax and mechanics

let's start simple. here's a parent class Animal, and two child classes Dog and Cat that extend it:

java — parent class
public class Animal {
    private String m_name;   // private — only Animal can access directly

    public Animal(String name) {
        this.m_name = name;   // store the name when created
    }

    // public getter — child classes can call this to get the name
    public String getName() {
        return m_name;
    }

    // generic animal sound — children will override this with their own version
    public void speak() {
        System.out.println("...");
    }
}
java — child classes that extend Animal
public class Dog extends Animal {

    public Dog(String name) {
        super(name);   // call Animal's constructor first — required!
    }

    @Override
    public void speak() {
        System.out.println("Woof!");   // Dog's own version of speak()
    }

    public void fetch() {
        // getName() is inherited from Animal — Dog doesn't need to rewrite it
        System.out.println(getName() + " fetches the ball!");
    }
}

public class Cat extends Animal {

    public Cat(String name) {
        super(name);
    }

    @Override
    public void speak() {
        System.out.println("Meow!");   // Cat's version is different from Dog's
    }
}

// Using them:
Dog d = new Dog("Rex");
d.speak();     // "Woof!" — Dog's version
d.getName();  // "Rex"  — inherited from Animal, free!
d.fetch();    // "Rex fetches the ball!"

Cat c = new Cat("Luna");
c.speak();     // "Meow!" — Cat's version, different from Dog's
c.getName();  // "Luna"  — same inherited method, different data

so what does Dog inherit from Animal? every public and protected method and field. getName() is inherited and just works. m_name is private, so Dog can't access it directly — but it can call getName(), which is the proper way anyway. the data is there, Dog just goes through the getter.

the real WPILib version

now let's look at exactly this pattern but in actual FRC code. here's a simplified version of SubsystemBase and a real DriveSubsystem built on top of it:

java — SubsystemBase is Animal, DriveSubsystem is Dog
// SubsystemBase — WPILib gives you this, simplified here for clarity
public class SubsystemBase {

    public SubsystemBase() {
        // registers THIS subsystem with the CommandScheduler (CommandScheduler = the WPILib engine that runs all your Commands and calls periodic() on every subsystem every 20ms) automatically
        // this is why your subsystem gets periodic() called every 20ms without you doing anything
        CommandScheduler.getInstance().registerSubsystem(this);
    }

    // empty by default — you override this in your subsystem to do stuff every loop
    public void periodic() {}
}

// Your subsystem — inherits all of SubsystemBase's plumbing for free
public class DriveSubsystem extends SubsystemBase {

    private double m_leftSpeed  = 0.0;
    private double m_rightSpeed = 0.0;

    public DriveSubsystem() {
        super();   // runs SubsystemBase() — registers with CommandScheduler
                   // without this, periodic() would never be called on your subsystem
    }

    @Override
    public void periodic() {
        // runs every 20ms — this is where you'd update SmartDashboard (the display on the driver's laptop that shows live robot data), run PID loops (PID = a control algorithm that calculates how much power to apply to reach a target value, like a target speed), etc.
        updateDashboard();
    }

    public void drive(double left, double right) {
        m_leftSpeed  = left;
        m_rightSpeed = right;
        // in real code, you'd set the actual TalonFX motors here
    }
}

the pattern is identical to Animal/Dog. SubsystemBase has the common plumbing (registration, periodic hook). DriveSubsystem inherits all of that, overrides periodic() with its own behavior, and adds drive() which is unique to the drive system. ShooterSubsystem would do the exact same thing but with flywheel logic instead. IntakeSubsystem too. they all share the SubsystemBase foundation.

@Override — your typo catcher

you've seen @Override in every example above. let's actually talk about what it does, because it's more useful than you might think.

when you write @Override above a method, you're telling Java two things: first, "i am intentionally replacing a method from the parent class." second, "if i got the name or signature wrong, tell me NOW rather than silently letting it slip through."

here's the scenario where it saves you. imagine you meant to override periodic() but you accidentally typed Periodic() (capital P). without @Override, Java doesn't know you intended to override anything. it silently creates a brand new method called Periodic that is NEVER called by anyone. your code compiles, the robot starts, periodic logic never runs, and you have no idea why. you spend an hour debugging. with @Override, you get an instant compile error: "method does not override a method from its superclass." zero wasted time.

common gotcha: always use @Override when you intend to override a method. it's not required to make the override work, but skipping it removes a really useful safety net. the WRT style guide (and basically every Java style guide) requires it.

java — @Override catching a typo
public class DriveSubsystem extends SubsystemBase {

    // WITHOUT @Override — this compiles fine but never runs!
    // Java thinks you're just creating a new method called "Periodic" (capital P)
    public void Periodic() {     // silent bug — nobody ever calls this
        updateDashboard();
    }

    // WITH @Override — Java checks that periodic() exists in the parent
    @Override
    public void Periodic() {     // COMPILE ERROR: method does not override a method from its superclass
        updateDashboard();        // immediately tells you about the typo — much better!
    }
}

super() — finishing the parent first

when you create a Dog, Java needs to set up the Animal part of it before Dog can do its own setup. that's what super() is for. it calls the parent class's constructor. Java requires this to be the very first line of the child constructor — no exceptions, no workarounds.

think about why this rule makes sense. if Dog tries to set up its own fields before Animal has run its constructor, those fields might depend on stuff Animal was supposed to initialize. you'd get weird null pointer errors or garbage values. Java enforces "parent first, child second" to prevent this entire category of bugs.

java — super() and super.method()
public class Dog extends Animal {
    public Dog(String name) {
        super(name);   // MUST be first line — calls Animal(String name)
                        // Animal sets up m_name, THEN Dog can do its own setup
    }

    @Override
    public void speak() {
        super.speak();            // optional: run Animal's version first ("...")
        System.out.println("Woof!");  // then add Dog's own behavior on top
    }
}

// In FRC: ShooterSubsystem calling super() on SubsystemBase
public class ShooterSubsystem extends SubsystemBase {
    public ShooterSubsystem() {
        super();   // SubsystemBase registers this subsystem with CommandScheduler
                   // if you forget this, subsystem never gets registered, periodic() never runs
    }
}

you can also use super.methodName() (not just super()) to call the parent's version of a method from inside the overriding method. you'll see this sometimes in FRC when a child command wants to run the parent's behavior AND add its own on top. it's optional — most of the time you just replace the parent method entirely.

the "is-a" test — know when to use inheritance

not every relationship between classes should use inheritance. here's a simple mental test: can you honestly say "[child class] IS-A [parent class]" and have it be true in a real-world sense? if yes, inheritance is probably the right call. if no, you should probably use composition (store it as a field) instead.

  • Dog IS-A Animal — yes, that's true in the real world. inheritance makes sense.
  • DriveSubsystem IS-A SubsystemBase — yes. it's a kind of subsystem. extends is right.
  • ShooterSubsystem IS-A SubsystemBase — yes. same deal.
  • Motor IS-A SubsystemBase — no. a motor is a hardware component the subsystem uses, not a subsystem itself. the motor should be a field inside the subsystem, not a parent class.
  • Constants IS-A Robot — definitely no. constants are just data, not a kind of robot.

when the is-a test fails, it usually means you want composition instead — you store the other thing as a field (has-a relationship). DriveSubsystem HAS-A TalonFX (TalonFX = the motor controller our team uses, made by CTRE) motor. it doesn't extend TalonFX.

one important limit: in Java, a class can only extend ONE parent class. this is called single inheritance. so DriveSubsystem extends SubsystemBase is fine, but you can't write DriveSubsystem extends SubsystemBase, SomeOtherClass. Java doesn't allow it. if you need multiple capabilities, that's what interfaces are for — topic 3!

extends
Inherit from a class
Child gets all public/protected methods and fields from parent. Can only extend one class (single inheritance). Child adds its own stuff on top.
super()
Call parent constructor
Must be the very first line in a child constructor. Parent gets set up fully before child adds its own fields. Java enforces this rule.
@Override
Replace a parent method
Tells Java you're intentionally replacing a method. If the parent doesn't have a matching method, Java errors immediately — catching typos before they become silent bugs.
is-a test
Sanity check for inheritance
Ask: "Child IS-A Parent — is that actually true?" DriveSubsystem IS-A SubsystemBase — yes. Motor IS-A SubsystemBase — no, that's a has-a, use a field instead.

Topic 1 — Coding Prompt

Build an Inheritance Hierarchy
Parent class + two children, like WPILib subsystems

Write a parent class FRCSubsystem with:
• private String m_name field
• a constructor that takes a name string
• a getName() getter
• a periodic() method that prints "periodic: [name]"

Then write two child classes that extend it:
Drivetrain: constructor calls super(name), overrides periodic() to print "driving: [name]"
Shooter: constructor calls super(name), overrides periodic() to print "shooting: [name]"

In main, create one of each and call periodic() on both. Use @Override on both child methods.

Topic 1 — Quick Check


Abstract Classes

ok so now you know what inheritance is. abstract classes are the next step up. they add one more idea on top: some methods in this parent class are mandatory. every child MUST implement them, or Java won't compile. it's a way of enforcing a contract between the parent and all of its children.

you've been using abstract classes since the moment you wrote extends Command. now let's understand why.

abstract classes — the partial blueprint

imagine a paper form with some fields already filled in — like the date, the school name, the instructions at the top — and some fields that are intentionally left blank for you to fill in. the pre-filled fields are done. your job is the blanks. and critically: you cannot hand in an incomplete form. if you haven't filled in your name or answers, it's rejected.

an abstract class is exactly like that form. it has some methods with real implementations already written (called concrete methods — the pre-filled fields). and it has some methods with no body at all (called abstract methods — the blanks you MUST fill in). if a child class doesn't fill in all the blanks, Java refuses to compile. and just like you can't submit the blank form itself as a finished submission, you can't directly create an object from an abstract class.

the key insight is: abstract classes let you say "here's a bunch of functionality I've already written for you, AND here's a list of things you must provide yourself." it's a partial blueprint that requires some customization before it's usable.

Command in WPILib is basically an abstract class. it has default implementations for initialize(), execute(), end(), and isFinished(). you pick which ones to override based on what your command needs to do. WPILib could have made some of them truly abstract (required to override), but it chose to give defaults so you only have to write what you care about. the pattern is the same though: parent provides structure, child fills in the details.

abstract methods — the mandatory blanks

an abstract method has NO body. it's just a declaration — a type, a name, and a semicolon. no curly braces, no code inside. it's a promise that says "any concrete (non-abstract) subclass WILL have this method implemented."

java — abstract class with both kinds of methods
public abstract class Shape {

    // abstract method — no body! every subclass MUST implement this
    public abstract double area();

    // concrete method — fully implemented, subclass can use it as-is
    // notice it CALLS area() even though area() has no body here
    // that's fine — when executed on a real subclass, that subclass's area() will run
    public void describe() {
        System.out.println("I am a shape with area: " + area());
    }
}

public class Circle extends Shape {
    private double m_radius;

    public Circle(double radius) {
        m_radius = radius;
    }

    @Override
    public double area() {
        return Math.PI * m_radius * m_radius;   // blank filled in!
    }
    // describe() is inherited for free — no need to rewrite it
}

public class Rectangle extends Shape {
    private double m_width;
    private double m_height;

    public Rectangle(double w, double h) {
        m_width = w;
        m_height = h;
    }

    @Override
    public double area() {
        return m_width * m_height;   // different implementation of the same contract
    }
}

// This would be a compile error — Shape has unfilled blanks (area()):
// Shape s = new Shape();  ← ERROR: Shape is abstract; cannot be instantiated

// These are fine — Circle and Rectangle filled in all the blanks:
Circle    c = new Circle(5.0);
Rectangle r = new Rectangle(3.0, 4.0);

c.describe();   // "I am a shape with area: 78.53..." — concrete method inherited!
r.describe();   // "I am a shape with area: 12.0"

one thing worth noting: the describe() method in Shape calls area() — even though Shape.area() has no body. this is fine. when describe() actually runs on a Circle or Rectangle, Java uses that specific subclass's version of area(). the abstract method is just a placeholder during the parent's code; the real implementation gets filled in at runtime. this is called polymorphism, and it's really powerful.

you CANNOT instantiate an abstract class — here's why

the compile error you get when you try to do new Shape() is intentional. abstract classes have abstract methods with no body. if Java let you create a Shape object and you called shape.area(), there would be NO code to run. the method literally doesn't exist yet. Java prevents this at compile time so you never get that crash at runtime.

you MUST extend the abstract class in a concrete subclass first. once you've filled in all the blanks (implemented all abstract methods), Java allows you to create objects of that subclass.

danger: if you extend an abstract class but forget to implement one of its abstract methods, your subclass becomes abstract too (even without the abstract keyword). Java will refuse to let you instantiate it and will give you a compile error listing exactly which methods you forgot to implement.

the WPILib Command pattern — abstract in the real world

here's the Command pattern from WPILib, which is the most important abstract class you'll use all season. note that the actual WPILib Command is more complex, but this captures the key idea:

java — simplified WPILib Command (the template)
// WPILib gives you this — it's effectively an abstract class
public abstract class Command {

    // concrete with defaults — override ONLY what you need
    public void    initialize() {}                      // runs once when command starts
    public void    execute()    {}                      // runs every 20ms while command is active
    public void    end(boolean interrupted) {}          // runs once when command finishes
    public boolean isFinished() { return false; }      // return true to end the command naturally

    // utility method — concrete, fully implemented for you
    public final void addRequirements(Subsystem... requirements) { /* ... */ }
}

// Your command overrides only what matters for this specific command
public class DriveCommand extends Command {
    private final DriveSubsystem m_drive;
    private final Joystick       m_joystick;   // Joystick = the controller the driver uses during matches

    public DriveCommand(DriveSubsystem drive, Joystick joystick) {
        m_drive    = drive;
        m_joystick = joystick;
        addRequirements(drive);   // inherited concrete method — tells scheduler this command owns the drive
    }

    @Override
    public void execute() {
        // this is the only part unique to driving — fill in only this blank
        m_drive.drive(m_joystick.getY(), m_joystick.getRawAxis(1));
    }

    // isFinished() not overridden — defaults to false, runs until interrupted by another command
    // initialize() not overridden — nothing to set up for a continuous drive command
    // end() not overridden — drive(0,0) gets called by the next command automatically
}

see how clean that is? you wrote exactly one method. the framework handles everything else. that's the power of abstract classes — WPILib designed a template where you only fill in the parts that are unique to your use case.

abstract vs concrete — when does a method have a body?

here's the distinction laid out clearly:

Method typeHas a body?Can subclass override it?Must subclass implement it?
abstract methodNo — declaration onlyN/A, must implement itYes — compile error if not
concrete method (in abstract class)Yes — full implementationOptionally, with @OverrideNo — use the default or override
concrete method (in regular class)YesOptionally, with @OverrideNo
abstract class
The template / form
Has some implemented concrete methods and some abstract (blank) ones. Can't be instantiated directly — must be extended first.
abstract method
A mandatory blank
No body. Just a declaration. Non-abstract subclasses MUST provide a full implementation or they can't compile.
concrete method
A pre-filled field
Has an actual implementation. Subclasses inherit it for free and can optionally override it with their own version.
new Command() = error
Can't instantiate abstract
Abstract classes have unfilled blanks. Creating one directly would produce an object with missing behavior. Extend it and fill the blanks first.

Topic 2 — Coding Prompt

Abstract Robot Action
Build an abstract base class, then make two concrete actions

Write an abstract class RobotAction with:
• abstract method String getDescription()
• abstract method boolean isDone()
• a regular (non-abstract) method void printStatus() that prints: "Action: [getDescription()] | Done: [isDone()]"

Now make two concrete classes that extend it:
DriveForward: description = "Driving forward", isDone = false
StopAll: description = "Stopping all motors", isDone = true

In main, create one of each and call printStatus(). As a bonus: try writing new RobotAction() — what error do you get and why?

Topic 2 — Quick Check


Interfaces

now here's where it gets interesting. inheritance is great, but it has one hard limit: a class can only extend ONE parent. if DriveSubsystem already extends SubsystemBase, it can't also extend some other class for logging, or some other class for tuning. you're stuck with one parent.

interfaces solve this. they let you layer in extra "capabilities" on top of whatever class hierarchy you already have, without the single-inheritance restriction.

interfaces — what you can do, not what you are

think of a job posting. the posting says "to get this job, you must be able to: write code, do code reviews, and attend stand-up meetings." it doesn't tell you HOW to write code or what your code review process looks like. it just says you MUST be able to do those things. you sign the offer letter, you're promising to fulfill that contract.

an interface is exactly that. it's a list of method signatures (names, parameters, return types) with NO implementations. any class that says implements MyInterface is making a legal promise: "i will provide all of these methods." Java enforces this promise at compile time.

the critical difference from abstract classes: interfaces define what a class can do (capabilities), not what it is (identity). Loggable is a capability. SubsystemBase is an identity. Stoppable is a capability. Command is an identity.

you can only extend one parent, but your subsystem might need to do multiple things that don't come from SubsystemBase. it might need to be loggable (send data to a custom logger). it might need to be stoppable (emergency stop behavior). it might need to be tunable (adjustable PID gains from dashboard). interfaces let you layer all of those capabilities onto a class that already has a parent.

defining and implementing an interface

java — a simple Loggable interface
// Interface definition — just a contract, no bodies, no fields
public interface Loggable {
    String getLogData();   // no body — any Loggable MUST implement this
}

// A class that extends AND implements at the same time
public class DriveSubsystem extends SubsystemBase implements Loggable {

    private double m_leftSpeed  = 0.0;
    private double m_rightSpeed = 0.0;

    // must implement getLogData() — Loggable contract requires it
    @Override
    public String getLogData() {
        return "Left: " + m_leftSpeed + " Right: " + m_rightSpeed;
    }

    // still overrides periodic() from SubsystemBase like normal
    @Override
    public void periodic() {
        System.out.println(getLogData());   // use our own Loggable method in periodic
    }
}

the key word is implements — not extends. you extend a class (there's one parent). you implement an interface (you can have as many as you want). the order in the class signature is always: extends first, then implements.

implementing multiple interfaces — this is the big deal

here's where interfaces really shine over abstract classes. a class can implement as many interfaces as it wants, separated by commas:

java — stacking multiple interfaces
// Two separate interfaces — separate capabilities
public interface Loggable {
    String getLogData();   // "i can describe my own state"
}

public interface Stoppable {
    void stop();   // "i have an emergency stop"
}

// extends ONE class, implements MULTIPLE interfaces
public class DriveSubsystem extends SubsystemBase implements Loggable, Stoppable {

    private double m_leftSpeed  = 0.0;
    private double m_rightSpeed = 0.0;

    @Override
    public String getLogData() {
        return "Drive — L: " + m_leftSpeed + " R: " + m_rightSpeed;
    }

    @Override
    public void stop() {
        m_leftSpeed  = 0.0;
        m_rightSpeed = 0.0;
        // in real code: set TalonFX outputs to 0.0
    }

    @Override
    public void periodic() { /* normal periodic logic */ }
}

// ShooterSubsystem can implement the same interfaces independently
public class ShooterSubsystem extends SubsystemBase implements Loggable, Stoppable {

    private double m_flywheelSpeed = 0.0;

    @Override
    public String getLogData() {
        return "Shooter — flywheel: " + m_flywheelSpeed;   // different data, same contract
    }

    @Override
    public void stop() {
        m_flywheelSpeed = 0.0;
    }
}

both DriveSubsystem and ShooterSubsystem implement Loggable and Stoppable — but each one provides its OWN implementation of those methods. the contract is the same ("you must have getLogData() and stop()"), but the behavior is completely different per class. this is polymorphism working through interfaces.

using interfaces as types — the real power move

here's the part that makes interfaces really powerful in practice. because every Loggable class is guaranteed to have getLogData(), you can write code that accepts ANY Loggable and calls that method without knowing what the actual class is:

java — coding to interfaces, not specific classes
// This logger works with ANY class that implements Loggable
// it doesn't know or care if it's a DriveSubsystem or a ShooterSubsystem
public class RobotLogger {
    public void log(Loggable target) {
        System.out.println("[LOG] " + target.getLogData());
    }
}

// And you'd use it like this:
RobotLogger      logger = new RobotLogger();
DriveSubsystem   drive  = new DriveSubsystem();
ShooterSubsystem shoot  = new ShooterSubsystem();

logger.log(drive);   // "Drive — L: 0.0 R: 0.0"
logger.log(shoot);   // "Shooter — flywheel: 0.0"

// Same method call, different behavior based on the actual object type
// the log() method doesn't need to know which type it is — that's the whole point

FRC: the Sendable interface

here's a real interface from WPILib that your code already uses: Sendable. any class that implements Sendable can send its data to Shuffleboard (the dashboard running on the driver station laptop). you implement initSendable() and WPILib handles polling your data and displaying it automatically.

here's the cool part: SubsystemBase already implements Sendable. so when you extend SubsystemBase, your subsystem inherits Sendable behavior too. that's why your subsystems show up on Shuffleboard without you having to do any extra work. it's inheritance and interfaces both working together at the same time.

WPILib also has interfaces like MotorController (a contract for anything that controls a motor — TalonFX, SparkMax, etc.), and Encoder. writing code against those interfaces means your code works with any hardware that implements the contract, not just one specific brand of motor controller (motor controller = the hardware that takes commands from the robot's computer and controls the motor's speed).

interface vs abstract class — the comparison table

this is the question everyone asks once they've seen both. here's the clear breakdown:

Abstract ClassInterface
Can have fields?Yes — instance variables allowedNo — no instance variables (only constants)
Can have concrete methods?YesYes, with default keyword (Java 8+)
Can have abstract methods?YesAll methods are abstract by default
How many can a class use?One (single inheritance)As many as you want
Keyword used by childextendsimplements
Best for"IS-A" relationships, shared state"CAN-DO" capabilities, multiple contracts
WPILib exampleSubsystemBase, CommandSendable, MotorController

quick rule: if it's a capability ("can do X"), use an interface. if it's an identity ("is a type of Y"), use an abstract class or regular class. Loggable is a capability — interface. SubsystemBase is an identity — abstract class. Sendable is a capability — interface. Command is an identity — abstract class. if you're still not sure, ask yourself: does this thing need shared state (fields)? if yes, abstract class. if no, probably interface.

the interface gotcha — no instance variables

this is the one thing that bites people when they first try to write interfaces: you cannot put instance variables (regular fields) in an interface. only constants (public static final) are allowed.

java — what's allowed in an interface
public interface Loggable {

    // constants are fine — public static final is implied
    int kMaxLogLength = 256;   // implicitly public static final

    // abstract method — fine, this is what interfaces are for
    String getLogData();

    // default method (Java 8+) — provides a default implementation
    default void printLog() {
        System.out.println(getLogData());
    }

    // THIS IS NOT ALLOWED — no instance variables in interfaces
    // String m_name;   ← COMPILE ERROR
    // int m_count = 0; ← COMPILE ERROR
}

the reason is conceptual: interfaces don't hold state. they define behavior. if you need shared state (fields), that's a sign you want an abstract class, not an interface. an interface just says "you must have these methods." what data you use to implement those methods is YOUR class's business, not the interface's.

interface
A contract / capability
No instance fields, just method signatures. A class promises to implement all of them using the implements keyword.
implements
Sign the contract
Use implements (not extends) to adopt an interface. You must implement every method the interface declares, or get a compile error.
multiple interfaces
Stack capabilities freely
A class can implement as many interfaces as needed. implements Loggable, Stoppable, Sendable is totally valid. One parent, many contracts.
Sendable
WPILib real example
Implementing Sendable lets a class push data to Shuffleboard. SubsystemBase already implements it, so your subsystems get this for free via inheritance.

Topic 3 — Coding Prompt

Multi-Capability Subsystem
Combine inheritance + multiple interfaces like a real FRC class

Step 1 — write two interfaces:
Stoppable: one method — void stop()
Reportable: one method — String getStatus()

Step 2 — write a parent class BaseSubsystem with a private String m_name, a constructor, and a getName() getter.

Step 3 — write IntakeSubsystem extends BaseSubsystem implements Stoppable, Reportable. Give it a boolean m_running field. Add a run() method that sets it to true. Implement stop() (sets to false) and getStatus() which returns "Intake [name]: running=[true/false]".

In main: create one, call run(), print status, call stop(), print status again.

Topic 3 — Quick Check


Fill in the Blanks

// DriveSubsystem inherits from SubsystemBase
public class DriveSubsystem SubsystemBase { }
// Tell Java you're intentionally replacing the parent's speak() method

public void speak() { System.out.println("Woof"); }
// Shooter signs the Loggable contract
public class Shooter Loggable { }
// Call the parent constructor (must be first line)
public Dog(String name) {
    // parent constructor
}

Knowledge Check

quick check on the concepts before the weekly test. no pressure!


Project Task — Week 6
ShooterSubsystem.java

Create ShooterSubsystem.java in your minibot-project folder. Like DriveSubsystem, this models a real FRC shooter using plain Java — no WPILib needed. This week you'll use inheritance to structure it the way WPILib expects so the pattern is familiar when you get to the offseason.

  • Class signature: public class ShooterSubsystem extends SubsystemBase — add a comment // SubsystemBase is a WPILib class; write the extends now so the pattern sticks and just don't import it for now (your plain Java file won't compile with it, and that's fine)
  • Private fields: int m_topMotorID, int m_bottomMotorID, double m_targetRPS, boolean m_isSpinning
  • Constructor: initialize motor IDs from Constants.ShooterK
  • public void spinUp(double targetRPS) — sets target, sets isSpinning to true
  • public void stop() — resets target to 0, isSpinning to false
  • public boolean isAtSpeed() — returns true if isSpinning AND targetRPS >= Constants.ShooterK.kSpeedThreshold
  • public String getLogData() — implement a method that returns a log string (this is like implementing an interface)
  • Javadoc on every public method
week 7 will bring DriveSubsystem and ShooterSubsystem together in a RobotState class!

Weekly Test

covers everything from week 6. inheritance is one of the most tested topics in Java interviews ngl. score goes to leads!!

week 6 test
inheritance, abstract classes, interfaces · 8 questions!!