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

Advanced Classes

enums, nested classes, ArrayLists, and wrapper classes. patterns you'll see constantly in real robot code :)

Enums

we use enums constantly on 2974. robot states, game piece types (whatever object robots pick up and score in that year's FRC game), scoring positions, arm positions — all enums. if you forget this week, you won't be able to read our codebase. ngl this is one of the most important weeks.

before we get into syntax, let's talk about the problem enums solve, because the "why" here is way more important than the "how."

the enum mental model

imagine you're at a restaurant. you can't just walk up to the counter and say "i want a unicorn steak with a side of moon rocks." you have to order from the menu. the menu is a fixed list of valid options — you pick one of those, nothing else. an enum is the menu. you define the exact options upfront, and Java will only allow those options. nothing else compiles. nothing else runs.

that sounds limiting, but it's the entire point. before enums existed, people tracked robot state with strings or integers. string "TELEOP" works great — until someone types "Teleop" (capital T) or "TELE0P" (zero instead of an O). Java doesn't catch either of those. the robot just behaves wrong and you spend an hour debugging a typo. an integer 0 for disabled, 1 for teleop, 2 for auto works great — until someone passes 7 and you have no idea what that means. enum fixes all of this: only the exact declared values are valid, and the compiler checks it, not you at runtime.

arm positions, drivetrain modes, game piece types, scoring locations, LED states — all of these are "pick from a list" problems. the codebase uses enums for basically all of them. if you see a field declared as ArmState m_state in a WRT subsystem, that's an enum. if you see a switch statement checking m_state, that's an enum. this is the most foundational pattern in how we structure behavior.

the String problem in detail: say you track robot mode with String m_mode = "TELEOP". three weeks later, your teammate writes a condition: if (m_mode == "Teleop") — different capitalization. Java won't warn you. the condition is always false. the robot runs teleop code but your condition never triggers. good luck finding that bug at midnight before a competition. enum makes this literally impossible to compile.

declaring an enum

you use the enum keyword instead of class. the values inside are written in SCREAMING_SNAKE_CASE by convention — they're constants, so they get the constant treatment. the name of the enum follows PascalCase like any other type.

java
// declare the enum — like a class but for a fixed set of values
public enum RobotState {
    DISABLED, TELEOP, AUTO, TEST
}

// using it — type is RobotState, value must be one of the four
RobotState state = RobotState.TELEOP;

// comparing with == — no .equals() needed for enums!
if (state == RobotState.TELEOP) {
    System.out.println("running teleop");
}

// this won't even compile — that's the whole point
// RobotState bad = "TELEOP";   <-- compile error, good!
// RobotState bad = 1;          <-- compile error, also good!

why SCREAMING_SNAKE_CASE? enum values are implicitly static final constants — they're created once and never change. Java convention says constants should be all caps with underscores. so INTAKE_POSITION, SCORE_HIGH, FIELD_RELATIVE — not intakePosition or scoreHigh. it visually signals "this is a fixed constant, not a variable."

enums + switch statements (this combo is chef's kiss)

enums and switch statements are designed for each other. you switch on the enum value, and Java can warn you if you miss a case — it knows exactly how many possible values the enum has. compare that to switching on a string or integer where the compiler has no idea how many valid values exist.

java
RobotState state = RobotState.TELEOP;

switch (state) {
    case TELEOP:
        runTeleopCode();
        break;
    case AUTO:
        runAutoCode();
        break;
    case DISABLED:
        stop();
        break;
    default:
        stop();
}

key syntax detail: inside the switch you just write case TELEOP, not case RobotState.TELEOP. Java already knows the type from the switch expression — the enum name is redundant and leaving it out is correct style. outside a switch, you always need the full RobotState.TELEOP.

the "just use a String" gotcha (don't do this)

this is a mistake beginners make when they think "eh, what's the difference, a string works fine." let's show exactly what goes wrong. here's the fragile string version versus the correct enum version side by side:

java — BAD: string-based state (fragile)
// BAD: nothing stops invalid values from getting in
private String m_mode = "IDLE";

public void setMode(String mode) {
    m_mode = mode; // what if someone passes "idle"? or "IDEL"? or "auto "?
                   // no error, no warning. silent bug.
}

if (m_mode == "TELEOP") { // comparing strings with == is wrong anyway
    runTeleop();           // this might never run even if mode IS "TELEOP"
}
java — GOOD: enum-based state (safe)
// GOOD: the compiler enforces every value
public enum Mode { IDLE, TELEOP, AUTO, DISABLED }

private Mode m_mode = Mode.IDLE;

public void setMode(Mode mode) {
    m_mode = mode; // only valid Mode values can be passed — period
}

if (m_mode == Mode.TELEOP) { // == works correctly for enums
    runTeleop();
}

enums can have methods

this is where enums get really interesting — they're actually full classes under the hood. you can add fields, constructors, and methods to them. the most common pattern is a boolean helper method directly on the enum itself, so the logic lives right where the values are defined.

java
public enum RobotState {
    DISABLED, TELEOP, AUTO, TEST;  // semicolon required when you add methods

    // helper method — call as state.isActive()
    public boolean isActive() {
        return this == TELEOP || this == AUTO || this == TEST;
    }

    // another useful pattern: human-readable label for logging
    public String getLabel() {
        switch (this) {
            case TELEOP:   return "Teleop";
            case AUTO:     return "Autonomous";
            case DISABLED: return "Disabled";
            default:       return "Test";
        }
    }
}

// usage
RobotState state = RobotState.TELEOP;
if (state.isActive()) {
    System.out.println("Robot is running: " + state.getLabel()); // "Robot is running: Teleop"
}

enums in FRC — the WRT state machine pattern

on 2974, enums power our state machines (a state machine tracks what "mode" the robot is in — like INTAKING, SHOOTING, or IDLE — and behaves differently in each). a subsystem has a "current state" (stored as an enum field), and the periodic loop checks that state and runs the right code. this replaces spaghetti if/else chains with a clean, readable, extensible pattern. when we need to add a new state, we add one value to the enum — we don't hunt through a chain of conditions trying to figure out where to insert a new branch.

java — simplified WRT pattern
public enum ArmState {
    STOWED, INTAKING, SCORING, CLIMBING
}

public class ArmSubsystem extends SubsystemBase {

    // current state lives as a field — m_ prefix because it's a member var
    private ArmState m_state = ArmState.STOWED;

    // command calls this to request a state change
    public void setState(ArmState newState) {
        m_state = newState;
    }

    // runs every 20ms — switch drives all behavior
    @Override
    public void periodic() {
        switch (m_state) {
            case INTAKING:  moveToIntakePos();  break;
            case SCORING:   moveToScorePos();   break;
            case CLIMBING:  moveToClimbPos();   break;
            default:        holdPosition();     // STOWED — hold where we are
        }
    }

    // expose state for other subsystems or dashboard
    public ArmState getState() { return m_state; }
}
type safety
Compiler-enforced values
Only the defined enum values are valid. typos become compile errors instead of silent runtime bugs.
readability
Self-documenting code
ArmState.SCORING is way clearer than 2 or "scoring". anyone reading the code instantly knows what it means.
switch
Perfect switch companion
switch on an enum covers every possible value. the compiler warns you if you miss a case.
methods
Behavior on the enum
you can add methods like isActive() directly to an enum. no separate helper class needed.

WRT convention: if you catch yourself writing private String m_state = "IDLE" in a subsystem, stop and refactor it to an enum. strings for state is a code smell we actively avoid. the PR reviewer will flag it every time.

Topic 1 — Coding Prompt

Shooter State Machine
Model a shooter subsystem using an enum and switch

Declare an enum ShooterState with four values: IDLE, SPOOLING, READY, FIRING.

Write a method void printAction(ShooterState state) with a switch on it:
IDLE → print "Motor off"
SPOOLING → print "Running at 50%"
READY → print "Running at 90%"
FIRING → print "Firing!"

In main, call it with each of the four states to confirm all cases work.

Topic 1 — Quick Check


ArrayList

you've used arrays before. you know the deal: declare the size upfront, access by index, done. but what happens when you don't know the size upfront? what if the size needs to change while the program is running? that's where ArrayList comes in.

ArrayList — arrays that grow

imagine a whiteboard where you write down a list of tasks. you can add new items at the bottom, erase items you finished, check if something is on the list — and the whiteboard is magic: it's as long as you need it to be. there's no "sorry, the whiteboard is full" — it just grows. that's an ArrayList.

contrast that with a regular array. a regular array is like a row of fixed lockers — you bolt them to the wall at construction time and you're stuck with that count forever. need one more locker? too bad. have an empty one? it just sits there wasting space. if you know you need exactly N items and that will never change, an array is great. but as soon as the count might change, ArrayList wins.

vision processing returns a variable number of detected targets — you might see 0, 1, or 5 game pieces and you don't know which until the camera tells you. a list of queued commands is dynamic — new commands get added and completed commands get removed. error logs, telemetry history, active subsystems — all of these have counts that change at runtime. that's ArrayList territory.

array vs ArrayList — the mental model

here's a quick rule of thumb that covers most cases: use an array when you KNOW the size upfront and it will never change. use ArrayList when the size is dynamic or unknown at compile time. a few examples from FRC to make it concrete:

java — when to use which
// ARRAY: exactly 4 swerve modules (each of the 4 wheel assemblies on a swerve robot — each one can drive and steer independently), always exactly 4 — fixed forever
int[] kModuleCanIDs = {1, 2, 3, 4}; // CAN ID — each motor on the robot needs a unique number so the code knows which one to talk to

// ARRAYLIST: unknown number of vision targets — changes every frame
ArrayList<String> detectedTargets = new ArrayList<>();

// ARRAY: exactly 3 PID constants — fixed (kP = how aggressively to correct error, kI = correction for persistent error, kD = dampens overshooting)
double[] kPIDConstants = {0.5, 0.0, 0.1};

// ARRAYLIST: log of recent state transitions — grows over time
ArrayList<String> m_stateLog = new ArrayList<>();

importing and creating an ArrayList

ArrayList lives in java.util, which is NOT automatically imported. you have to explicitly import it at the top of your file. then you create one with a type parameter in angle brackets — that type parameter says what kind of things this list holds.

java
// this import is REQUIRED — without it you get "cannot find symbol"
import java.util.ArrayList;

// ArrayList<String> means "a list that holds Strings"
ArrayList<String> names = new ArrayList<>();

// the <> on the right is the diamond operator — Java infers the type
// you could write new ArrayList<String>() but <> is cleaner and preferred

// ArrayList of integers (note: Integer, not int — covered in topic 3)
ArrayList<Integer> motorIDs = new ArrayList<>();

// starting with some items — use add() after creation
ArrayList<String> subsystems = new ArrayList<>();
subsystems.add("Drivetrain");
subsystems.add("Shooter");
subsystems.add("Intake");

what's the <String> thing? that's called a generic type parameter. it tells Java what type of objects this list holds. think of it as labeling a box "only apples go in here." if you try to add an orange (Integer) to an apple box (ArrayList<String>), Java stops you at compile time. always specify the type. an untyped raw ArrayList is legal but a code smell.

the core methods — add, get, size, contains, remove, clear

these six methods handle probably 90% of everything you'll do with an ArrayList. learn them cold:

java
ArrayList<String> subsystems = new ArrayList<>();

// add() — appends to the end of the list
subsystems.add("Drivetrain");   // list: [Drivetrain]
subsystems.add("Shooter");      // list: [Drivetrain, Shooter]
subsystems.add("Intake");       // list: [Drivetrain, Shooter, Intake]

// get() — access by index, zero-based just like arrays
String first = subsystems.get(0);  // "Drivetrain"
String second = subsystems.get(1); // "Shooter"

// size() — how many elements are currently in the list
int count = subsystems.size();    // 3

// contains() — checks if a value is in the list
boolean hasShooter = subsystems.contains("Shooter"); // true
boolean hasDrive   = subsystems.contains("Climber"); // false

// remove(object) — removes the first matching element
subsystems.remove("Intake");     // list: [Drivetrain, Shooter]
System.out.println(subsystems.size());      // 2

// isEmpty() — true if list has zero elements
System.out.println(subsystems.isEmpty());  // false

// clear() — removes everything from the list
subsystems.clear();
System.out.println(subsystems.size());      // 0
System.out.println(subsystems.isEmpty());  // true

iterating over an ArrayList with for-each

the for-each loop works identically with ArrayList as it does with arrays. the syntax is the same, the behavior is the same. just plug the ArrayList in where you'd put the array:

java
ArrayList<String> subsystems = new ArrayList<>();
subsystems.add("Drivetrain");
subsystems.add("Shooter");
subsystems.add("Intake");

// for-each: same as with arrays — element type, variable name, list
for (String name : subsystems) {
    System.out.println("active: " + name);
}
// active: Drivetrain
// active: Shooter
// active: Intake

// you can also use a regular index-based for loop if you need the index
for (int i = 0; i < subsystems.size(); i++) {
    System.out.println(i + ": " + subsystems.get(i));
}
// 0: Drivetrain
// 1: Shooter
// 2: Intake

the remove-by-index vs remove-by-value gotcha (this WILL bite you)

ok here's a subtle one. remove() is overloaded: you can call it with a value to remove (like remove("Intake")) or with an index to remove (like remove(2)). for an ArrayList of Strings, this is fine. but for an ArrayList<Integer>, there's a trap: Java will prefer the index version if you pass a plain int.

java — dangerous with Integer lists
ArrayList<Integer> ids = new ArrayList<>();
ids.add(10);
ids.add(20);
ids.add(30);

// this removes the element AT INDEX 1 (which is 20), not the value 1!
ids.remove(1); // list is now [10, 30] — might not be what you wanted

// to remove the value 10 specifically, wrap it in Integer.valueOf()
ids.remove(Integer.valueOf(10)); // now removes the value 10, not index 10

// for String lists this isn't an issue because String != int
ArrayList<String> names = new ArrayList<>();
names.add("Alice");
names.remove("Alice"); // unambiguous: removes the value "Alice"

common gotcha: list.remove(1) on an ArrayList<Integer> removes the element at index 1, NOT the element with value 1. to remove by value from an integer list, use list.remove(Integer.valueOf(yourValue)). this catches a lot of people off guard the first time.

array vs ArrayList — full feature comparison

FeatureArrayArrayList
sizefixed forever at creationgrows and shrinks dynamically
types allowedprimitives + objectsobjects only (use wrapper classes for primitives)
element accessarr[i]list.get(i)
length/sizearr.lengthlist.size()
add elementnot possible after creationlist.add(value)
remove elementnot possiblelist.remove(value)
check membershipmust loop manuallylist.contains(value)
use whenknown, fixed sizedynamic or unknown size

FRC real talk: in WPILib code you'll often see arrays for things that are truly fixed — the four module positions, the three PID gains, the set of auton options. but for anything that gets built up over time (vision targets, active commands, logs), ArrayList is the right tool. both show up, so you need to be comfortable with both.

Topic 2 — Coding Prompt

Vision Target Tracker
Use an ArrayList to manage a dynamic list of targets

Create an ArrayList<String> called targets and add four entries: "NoteA", "NoteB", "NoteC", "NoteD".

Then do each step and print as you go:
1. Print the size
2. Print the element at index 2
3. Remove "NoteB"
4. Print the size again
5. Check if "NoteB" is still in the list using contains() — print the result
6. Use a for-each loop to print all remaining targets

Don't forget import java.util.ArrayList; at the top!

Topic 2 — Quick Check


Wrapper Classes

you just learned that ArrayList only holds objects, not primitives. so ArrayList<int> won't compile. but you might very well need a list of integers. what do you do? Java's answer is wrapper classes — object versions of every primitive type.

wrapper classes — boxing up primitives

imagine you need to ship a piece of candy in the mail. you can't just tape a single Skittle to a postcard — it's too small and fragile, it doesn't have an address, and the post office doesn't know what to do with it. but if you put it in a box, suddenly it has all the structure the mail system needs: an address label, dimensions, packaging. the candy itself didn't change — you just wrapped it.

a primitive (like int) is the raw candy. a wrapper class (like Integer) is the box. the value inside is the same, but now it's a full object with methods, and it can go anywhere an object is expected — like inside an ArrayList.

once you start using collections (ArrayList, HashMap, etc.) you will constantly see Integer, Double, and Boolean in type parameters. you also use the static utility methods on wrapper classes — Integer.parseInt() is everywhere in config reading and dashboard input handling.

the primitive-to-wrapper mapping

every primitive type has a corresponding wrapper class. the pattern is just "capitalize it" — with two exceptions: int maps to Integer (not "Int") and char maps to Character (not "Char").

PrimitiveWrapper ClassExample use
intIntegerArrayList<Integer>
doubleDoubleArrayList<Double>
booleanBooleanArrayList<Boolean>
charCharacterArrayList<Character>
longLongArrayList<Long>
floatFloatArrayList<Float>

autoboxing — Java does the conversion for you

you might be thinking "do I have to manually wrap every int before adding it to a list?" — nope. Java has a feature called autoboxing that automatically converts primitives to their wrapper types when needed, and unboxing that converts wrapper types back to primitives. in practice this means you just write normal code and Java handles the boxing behind the scenes.

java
import java.util.ArrayList;

ArrayList<Integer> motorIDs = new ArrayList<>();

// autoboxing: int literal 5 is automatically wrapped into Integer(5)
motorIDs.add(5);   // you write int, Java stores Integer — transparent
motorIDs.add(12);
motorIDs.add(3);

// unboxing: Integer from the list is automatically unwrapped to int
int first = motorIDs.get(0); // Java unboxes Integer → int automatically
System.out.println(first);  // 5

// doubles work the same way
ArrayList<Double> speeds = new ArrayList<>();
speeds.add(0.75);    // autoboxed: double → Double
speeds.add(-1.0);
double s = speeds.get(0); // unboxed: Double → double
System.out.println(s);   // 0.75

the mental model: autoboxing and unboxing happen at the Java compiler level. when you write list.add(5), the compiler silently rewrites it to list.add(Integer.valueOf(5)). when you write int x = list.get(0), it silently becomes int x = list.get(0).intValue(). you never see it, but it's happening. this is why autoboxing has a tiny performance cost — it's creating objects. in robot code it's never a bottleneck, but worth knowing.

useful static methods on wrapper classes

wrapper classes aren't just containers for primitives — they come packed with handy static utility methods. these are some of the most-used methods in all of Java programming, not just FRC:

java
// parseInt / parseDouble — convert Strings to numbers
// used constantly for reading config values, dashboard input, etc.
int    port  = Integer.parseInt("5800");    // int 5800
double speed = Double.parseDouble("0.75");  // double 0.75

// parse fails loudly if the string isn't a valid number
// Integer.parseInt("abc") throws NumberFormatException — good to know

// MAX_VALUE / MIN_VALUE — boundary constants
int biggest  = Integer.MAX_VALUE;  // 2147483647 (2^31 - 1)
int smallest = Integer.MIN_VALUE;  // -2147483648

// toString — convert number to String
String s  = Integer.toString(42);   // "42"
String sd = Double.toString(3.14); // "3.14"

// valueOf — explicitly create a wrapper object from a primitive
Integer boxed = Integer.valueOf(99); // explicit boxing (autoboxing does this for you normally)

the NullPointerException trap (this WILL bite you)

here's the nastiest gotcha with wrapper classes, and it trips up intermediate programmers more than beginners. because wrapper classes are objects (not primitives), they can be null. that's fine for storage. the problem is when you try to unbox a null wrapper back to a primitive — Java throws a NullPointerException. and NPEs are runtime errors, not compile errors, which means the program compiles just fine and then crashes on a specific code path.

java — the null trap
// BAD: unboxing null crashes at runtime
Integer val = null; // Integer is an object — null is valid here

int x = val; // NullPointerException! Java tries to call val.intValue()
             // but val IS null — you can't call methods on null

// ─────────────────────────────────────────────────────────────────
// SAFE pattern: null-check before unboxing
Integer maybeNull = getSomeValueThatMightBeNull();

if (maybeNull != null) {
    int safe = maybeNull; // only unbox when you know it's not null
    doSomething(safe);
} else {
    handleMissingValue();
}

// or use a default with a ternary
int withDefault = (maybeNull != null) ? maybeNull : 0;

in practice: this bites you most when reading from a Map (HashMap returns null for missing keys) or when a method signature returns a nullable Integer. if you're adding values to an ArrayList yourself and they're not null, you're fine. but any time a method CAN return null and you're unboxing the result, add a null check. your future self will thank you.

putting it all together — ArrayList + wrapper classes + enums

let's write a snippet that uses all three concepts from this week together. this is actually close to patterns you'll see in real subsystem code:

java — all three concepts together
import java.util.ArrayList;

public enum GamePiece { CONE, CUBE, NONE }

public class PieceTracker {

    // ArrayList of enum values — tracks pieces seen this match
    private ArrayList<GamePiece> m_history = new ArrayList<>();

    // ArrayList of wrapper Doubles — scores recorded over time
    private ArrayList<Double> m_scores = new ArrayList<>();

    public void recordScore(GamePiece piece, double points) {
        m_history.add(piece);   // autoboxing not needed — GamePiece IS an object
        m_scores.add(points);   // autoboxed: double → Double transparently
    }

    public int getConeCount() {
        int count = 0;
        for (GamePiece p : m_history) {
            if (p == GamePiece.CONE) count++;  // == works for enum values
        }
        return count;
    }

    public double getTotalScore() {
        double total = 0.0;
        for (Double s : m_scores) {
            total += s;  // unboxed: Double → double for the addition
        }
        return total;
    }
}

Topic 3 — Coding Prompt

Motor ID Manager
ArrayList<Integer> with autoboxing and wrapper utilities

Create an ArrayList<Integer> called motorIDs and add five CAN IDs: 1, 5, 8, 12, 20.

Then do each step in order:
1. Print how many IDs are in the list
2. Remove the ID at index 2 using remove(2) — that's the value 8
3. Add a new ID by converting the String "15" to an int using Integer.parseInt()
4. Print whether 5 is still in the list using contains(5)
5. Use a for-each loop to print all remaining IDs

Don't forget import java.util.ArrayList;!

Topic 3 — Quick Check


Fill in the Blanks

// Declare a RobotState enum with TELEOP and AUTO
public RobotState { TELEOP, AUTO }
// Create an ArrayList of Strings
ArrayList<> names = new ArrayList<>();
// Get the second element from list
String s = list.;
// ArrayList that holds integers (use wrapper)
ArrayList<> ids = new ArrayList<>();
// Parse the String "42" into an int
int x = Integer.;

Knowledge Check


Coding Challenge

practice the ArrayList operations before the project task. try to predict the output before running.

java — what does this print?
import java.util.ArrayList;

ArrayList<String> list = new ArrayList<>();
list.add("alpha");
list.add("beta");
list.add("gamma");
list.remove("beta");
list.add("delta");

System.out.println(list.size());
System.out.println(list.get(1));
System.out.println(list.contains("beta"));

for (String s : list) {
    System.out.println(s);
}

answers: size() → 3, get(1) → "gamma" (after removing "beta", gamma shifts to index 1), contains("beta") → false. the foreach prints: alpha, gamma, delta.


Project Task — Week 7
RobotState.java

Create RobotState.java in your minibot-project folder — the glue that brings your MiniBot's subsystems together. This class tracks what the robot is currently doing and what it should do next.

  • Define an inner enum: public enum State { IDLE, INTAKING, DRIVING, SHOOTING, STOPPED }
  • Private fields: State m_currentState, ArrayList<String> m_stateHistory
  • Constructor: initialize state to State.IDLE, initialize the ArrayList
  • public void setState(State newState) — updates current state AND adds the state name to history
  • public State getState() — returns current state
  • public boolean isActive() — returns true if state is NOT IDLE and NOT STOPPED
  • public ArrayList<String> getHistory() — returns the history list
  • public String getLastAction() — returns the last entry in history, or "none" if empty
  • In main: create a RobotState, transition through several states, print the history
this class will be used in your final Robot.java in week 8!

Weekly Test

covers everything from week 7. almost done with the summer section!! score goes to leads :)

week 7 test
enums, ArrayList, wrapper classes · 8 questions!!