Skip to content

Intermediate Java

Java features used heavily in XBot code that go beyond the basics.

The concepts here appear in nearly every XBot subsystem. You will see them in the competition robot code and SeriouslyCommonLib.

1. Generics <T>

Generics let you write code that works with any type. Instead of writing the same logic for every type, you use a type parameter.

java
// Without generics: must cast, easy to make mistakes
List motors = new ArrayList();
motors.add(new TalonFX(1));
TalonFX m = (TalonFX) motors.get(0);  // Must cast, and what if it's not a TalonFX?

// With generics: type-safe, no casting needed
List<TalonFX> motors = new ArrayList<>();
motors.add(new TalonFX(1));
TalonFX m = motors.get(0);  // No cast needed -- Java knows it's a TalonFX

Common Generics You Will See

java
// Lists and Maps -- the most common
List<XCANMotorController> motors = new ArrayList<>();
Map<String, Double> pidValues = new HashMap<>();

// Optional
Optional<Distance> laserReading;

// Functional interfaces
Supplier<Double> valueSupplier;
Consumer<String> logger;

Why Generics Matter in XBot

Every List, Map, and Optional in the codebase uses generics. You cannot read or write XBot code without understanding < >.

java
// From TeamXbot2026 ShooterSubsystem -- storing motors in a typed list
public List<XCANMotorController> getShooterMotors() {
    var motors = new ArrayList<XCANMotorController>(3);
    // ... add motors
    return motors;
}

2. Collections (List, Map, ArrayList, HashMap)

Collections are objects that store groups of other objects. They are the standard way to manage multiple motors, configs, or data points.

List -- Ordered Collection

java
// List: ordered, can have duplicates
List<String> names = new ArrayList<>();
names.add("Drive");
names.add("Shooter");
names.add("Intake");
names.get(0);       // "Drive" -- access by index
names.size();       // 3
names.contains("Drive");  // true

// XBot pattern: storing all motors of a subsystem
List<XCANMotorController> shooterMotors = new ArrayList<>();
shooterMotors.add(leftMotor);
shooterMotors.add(rightMotor);

Map -- Key-Value Pairs

java
// Map: look up values by a key (like a dictionary)
Map<String, Double> motorSpeeds = new HashMap<>();
motorSpeeds.put("FrontLeft", 0.5);
motorSpeeds.put("FrontRight", 0.75);
motorSpeeds.get("FrontLeft");    // 0.5
motorSpeeds.containsKey("RearLeft");  // false

// XBot pattern: mapping configuration data
Map<CANBusId, List<CANDevice>> devicesByBus = new HashMap<>();
devicesByBus.computeIfAbsent(busId, k -> new ArrayList<>()).add(device);
ArrayList vs LinkedList -- which to use?

Use ArrayList 95% of the time. It is faster for most operations. LinkedList is only useful when you frequently add/remove items at the beginning of the list, which is rare in robot code.

OperationArrayListLinkedList
Get by indexInstantSlow (must walk the list)
Add at endInstantInstant
Add at beginningSlow (shifts elements)Instant
Memory overheadLowHigher (stores pointers)

3. Optional<T>

Optional is a box that either contains a value or is empty. It forces you to handle the "no value" case instead of crashing with a NullPointerException.

Creating Optional

java
Optional<String> name = Optional.of("Drive");   // Contains "Drive"
Optional<String> empty = Optional.empty();       // Contains nothing
Optional<String> maybe = Optional.ofNullable(someVariable);  // null becomes empty

Using Optional Safely

java
// BAD: might crash
XCANMotorController motor = getMotor();  // Could be null!
motor.setPower(0.5);                     // CRASH if null!

// GOOD: safe patterns

// 1. ifPresent -- do something only if value exists
getMotor().ifPresent(m -> m.setPower(0.5));

// 2. orElse -- provide a default if empty
double speed = getVelocity().orElse(RPM.zero()).in(RPM);

// 3. map -- transform the value if present
Optional<Distance> calibrated = getRawLaserDistance()
    .map(d -> d.minus(offset));

// 4. isPresent -- check then use (old-style, avoid if possible)
if (getMotor().isPresent()) {
    getMotor().get().setPower(0.5);
}

Why XBot Uses Optional

Hardware might not be connected during testing. Methods return Optional so the caller must consciously decide what to do when the hardware is absent.

java
// From SeriouslyCommonLib -- motor might not exist
public Optional<XCANMotorController> getMotorController() {
    return Optional.ofNullable(this.motor);
}

// Usage -- safe even if motor was never created
getMotorController().ifPresent(m -> m.setPower(0.5));

4. Lambdas ->

A lambda is a short anonymous function you can pass around as a value. Think of it as a snippet of code you can store in a variable or pass to another method.

java
// Full method:
public double doubleIt(double x) {
    return x * 2;
}

// Lambda version of the same thing:
(x) -> x * 2

Lambda Syntax

java
// Zero parameters
() -> System.out.println("Hello")

// One parameter (parentheses optional)
x -> x * 2
(x) -> x * 2

// Multiple parameters
(a, b) -> a + b

// Multiple statements (need braces and return)
(x) -> {
    double result = x * 2;
    System.out.println(result);
    return result;
}

Where XBot Uses Lambdas

java
// 1. With Optional -- only run if value exists
getMotor().ifPresent(m -> m.setPower(0.5));

// 2. With Streams -- transform/filter data
reefPoses.stream()
    .filter(pose -> pose.getX() > 0)
    .forEach(pose -> System.out.println(pose));

// 3. With property change listeners
maxOutputProps.hasChangedSinceLastCheck(
    (value) -> setPowerRange(minOutputProps.get(), value)
);

// 4. In command factories
new NamedRunCommand("Run", () -> motor.setPower(0.5), this);

// 5. With Suppliers (deferred values)
new WaitForDurationCommand(() -> getRemainingTime());

5. Method References ::

A method reference is a shorthand for a lambda that just calls one existing method.

java
// Lambda:
x -> System.out.println(x)

// Method reference (same thing):
System.out::println

Common Patterns

java
// Calling a method on a parameter
getMotor().map(m -> m.getVelocity())    // Lambda
getMotor().map(XCANMotorController::getVelocity)  // Method reference

// Calling a method with no parameters
() -> this.setForward()   // Lambda
this::setForward          // Method reference

// Static method
x -> Math.abs(x)          // Lambda
Math::abs                 // Method reference

// In XBot commands
new NamedRunCommand("Forward", this::setForward, this);
new NamedRunCommand("Stop", this::stop, this);

// With streams
motors.stream().forEach(XCANMotorController::setPower);

6. Streams (Functional Programming)

Streams let you process collections declaratively -- you say WHAT you want, not HOW to do it.

Without Streams (imperative)

java
List<Pose2d> reefPoses = getReefPoses();
List<Pose2d> filtered = new ArrayList<>();
for (Pose2d pose : reefPoses) {
    if (pose.getX() > 0) {
        filtered.add(pose);
    }
}

With Streams (declarative)

java
List<Pose2d> filtered = getReefPoses().stream()
    .filter(pose -> pose.getX() > 0)
    .collect(Collectors.toList());

Common Stream Operations

java
// FILTER -- keep items that match a condition
motors.stream()
    .filter(m -> m.getVelocity() > 0)
    .collect(Collectors.toList());

// MAP -- transform each item
reefPoses.stream()
    .map(Pose3d::toPose2d)
    .collect(Collectors.toList());

// FOR EACH -- do something with each item
motors.stream().forEach(m -> m.setPower(0.5));

// REDUCE -- combine all items into one
double total = values.stream().reduce(0.0, Double::sum);

// Find max
String longest = names.stream()
    .max(Comparator.comparing(String::length))
    .orElse("");

Real XBot Examples

java
// Converting blue to red alliance poses
public final List<Pose2d> redPoses = bluePoses.stream()
    .map(PoseSubsystem::convertBluetoRed)
    .collect(Collectors.toList());

// Finding max refresh rate
var maxEntry = refreshData.entrySet().stream()
    .max(Map.Entry.comparingByValue())
    .map(Map.Entry::getKey)
    .orElse(null);

// Calculating average of tag positions
double xTotal = tags.stream()
    .map(Pose2d::getX)
    .reduce(0.0, Double::sum);
What is a "functional programming" style?

Functional programming treats computation as evaluating functions and avoids changing state. Instead of writing loops that modify variables, you chain operations that describe the desired transformation.

Imperative (how):

java
List<Double> speeds = new ArrayList<>();
for (XCANMotorController m : motors) {
    if (m.isConnected()) {
        speeds.add(m.getVelocity().in(RPM));
    }
}

Functional (what):

java
List<Double> speeds = motors.stream()
    .filter(XCANMotorController::isConnected)
    .map(m -> m.getVelocity().in(RPM))
    .collect(Collectors.toList());

The functional version is shorter, clearer, and harder to accidentally break.

7. Enums

An enum (enumeration) is a fixed set of named constants. Use enums when a variable can only be one of a predefined set of values.

java
// Without enum: magic strings, error-prone
String state = "running";
if (state.equals("RUNNING")) { }  // Typo: "RUNNING" vs "running"?

// With enum: type-safe, autocomplete works
public enum MotorState { STOPPED, RUNNING, REVERSING }

MotorState state = MotorState.STOPPED;
if (state == MotorState.RUNNING) { }  // Safe, compiler catches typos

Enums in XBot

java
// Defining robot states
public enum ClimberState { RETRACTED, EXTENDED, MOVING }
public enum CoralLevel { L1, L2, L3, L4 }
public enum DeviceHealth { Healthy, Unhealthy }

// Switching on enums (modern switch)
return switch (state) {
    case RETRACTED -> deployClimber();
    case EXTENDED -> retractClimber();
    case MOVING -> waitForStop();
};

// Enums with fields (advanced)
public enum ReefFace {
    CLOSE_LEFT(0), CLOSE_RIGHT(1), FAR_LEFT(2), FAR_RIGHT(3);

    private final int index;
    ReefFace(int index) { this.index = index; }
    public int getIndex() { return index; }
}

Why Enums Matter

Enums replace "magic strings" and integer constants with readable, type-safe names. Every state machine in XBot uses them.

8. var Keyword

var lets the compiler figure out the type. It works only for local variables.

java
// Without var (verbose):
ArrayList<XCANMotorController> motors = new ArrayList<>();
HashMap<String, Double> values = new HashMap<>();

// With var (cleaner):
var motors = new ArrayList<XCANMotorController>();
var values = new HashMap<String, Double>();

When to Use var

Use varDon't Use var
When the type is obvious from the right sideWhen the type is unclear
var motors = new ArrayList<XCANMotorController>();var result = someMethod(); // What type is result?
var name = subsystem.getName(); // Clearly Stringvar value = complexOperation(); // What is this?

XBot Usage

java
var motors = new ArrayList<XCANMotorController>(3);
var distance = getRawLaserDistance();
var robotPose = new Pose3d(x, y, z, rotation);

9. static Members

static means the member belongs to the class itself, not to any one instance.

java
public class Units {
    public static final double INCHES_PER_METER = 39.37;  // Shared constant
    public static double metersToInches(double m) {        // Utility method
        return m * INCHES_PER_METER;
    }
}

// Usage -- no object needed, call on the class directly
double inches = Units.metersToInches(1.5);

Static Fields

java
public class RobotMap {
    public static final int DRIVE_MOTOR_PORT = 11;
    public static final int SHOOTER_MOTOR_PORT = 12;
    public static final double MAX_SPEED = 0.75;
}

// Access anywhere without creating a RobotMap object
int port = RobotMap.DRIVE_MOTOR_PORT;

Static Methods

java
public class PoseUtils {
    public static Pose2d convertBluetoRed(Pose2d bluePose) {
        return new Pose2d(-bluePose.getX(), bluePose.getY(), bluePose.getRotation());
    }
}

// Call without creating an instance
Pose2d redPose = PoseUtils.convertBluetoRed(bluePose);

Static Imports

java
// Without static import:
double rpm = Units.RPM.of(5000);

// With static import:
import static edu.wpi.first.units.Units.RPM;
double rpm = RPM.of(5000);  // Cleaner!

Static imports are used extensively in XBot for WPILib units (RPM, Meters, Seconds, etc.).

10. Modern switch Expressions

Java 14+ introduced a much cleaner switch:

java
// Old switch (easy to forget break):
String result;
switch (color) {
    case RED:
        result = "stop";
        break;
    case GREEN:
        result = "go";
        break;
    default:
        result = "unknown";
}

// Modern switch (no break, returns a value):
String result = switch (color) {
    case RED -> "stop";
    case GREEN -> "go";
    default -> "unknown";
};

XBot Examples

java
return switch (symmetry) {
    case kBlueOrigin -> new Pose2d(x, fieldWidth - y, rotation);
    case kRedOrigin -> new Pose2d(fieldLength - x, y, rotation);
};

return switch (alliance) {
    case Blue -> blueFaces;
    case Red -> redFaces;
};

11. Records

A record is a concise way to create a class that just holds data. Java auto-generates the constructor, getters, equals(), hashCode(), and toString().

java
// Traditional class (lots of boilerplate):
public class Point {
    private final double x;
    private final double y;
    public Point(double x, double y) { this.x = x; this.y = y; }
    public double x() { return x; }
    public double y() { return y; }
    @Override public boolean equals(Object o) { ... }
    @Override public int hashCode() { ... }
}

// Record (one line, same thing):
public record Point(double x, double y) { }

Records in XBot

java
// CAN bus identification
public record CANBusId(String id) { }

// Vision observation
public record VisionPoseObservation(
    Pose2d visionRobotPoseMeters,
    double timestampSeconds,
    int tagCount
) { }

// Scoring task data
public record ScoringTask(
    GameAction gameAction,
    Optional<ReefFace> reefFace,
    Optional<Branch> branch,
    Optional<CoralLevel> coralLevel
) { }

Use records whenever you have data that you want to pass around without behavior.

12. Builder Pattern

The Builder pattern constructs complex objects step by step. Instead of a constructor with many parameters (hard to read, easy to mix up), you chain method calls that each set one property.

java
// Without Builder: confusing constructor
var pidProps = new XCANMotorControllerPIDProperties(
    0.00005,  // p
    0.0,      // i
    0.0,      // d
    0.0,      // f
    1.0,      // maxPower
    -1.0      // minPower
);

// With Builder: clear what each value means
var pidProps = new XCANMotorControllerPIDProperties.Builder()
    .p(0.00005)
    .i(0)
    .d(0)
    .build();

How It Works

The builder has a method for each optional property. Each method returns this so calls can be chained. build() creates the final object.

java
public class MotorConfig {
    private final double p;
    private final double i;
    private final double d;

    // Private constructor -- only the Builder can call it
    private MotorConfig(double p, double i, double d) {
        this.p = p; this.i = i; this.d = d;
    }

    public static class Builder {
        private double p = 0;   // Default values
        private double i = 0;
        private double d = 0;

        public Builder p(double val) { this.p = val; return this; }
        public Builder i(double val) { this.i = val; return this; }
        public Builder d(double val) { this.d = val; return this; }

        public MotorConfig build() {
            return new MotorConfig(p, i, d);
        }
    }
}

// Usage:
var config = new MotorConfig.Builder()
    .p(0.00005)
    .d(0.01)
    .build();

Why XBot Uses Builders

Many XBot configuration objects have 5-10+ optional parameters. A constructor with that many arguments is unreadable -- you would have to count positions to know what each value means. The builder makes every value self-documenting.

java
// XBot real example: configuring a PID controller
var pidProperties = new XCANMotorControllerPIDProperties.Builder()
    .withVelocityFeedForward(0.01)
    .withMaxPowerOutput(1.0)
    .withMinPowerOutput(-1.0)
    .build();

// Another example: configuring swerve module limits
var moduleConfig = new SwerveModuleConfig.Builder()
    .wheelDiameter(Inches.of(4))
    .driveGearRatio(6.75)
    .steerGearRatio(12.8)
    .maxVelocity(FeetPerSecond.of(15))
    .build();

When to Use the Builder Pattern

Use BuilderDon't Use Builder
4+ constructor parameters1-2 simple parameters
Many optional parametersAll parameters required
Parameters where order is easy to mix upParameters where order is obvious
Configuration objects (PID values, limits)Simple data containers (use a Record instead)

Builders are overkill for simple objects. Use a Record for data holders with a few required fields, and a Builder for complex configuration objects with many optional fields.

13. Functional Interfaces

Functional interfaces are interfaces with a single method. They are the types that lambdas implement.

InterfaceMethodWhat It Does
Supplier<T>T get()Provides a value, takes nothing
Consumer<T>void accept(T)Takes a value, returns nothing
Predicate<T>boolean test(T)Tests a condition
Function<T,R>R apply(T)Transforms T into R

Supplier -- Deferred Values

java
Supplier<Double> timeSupplier = () -> getRemainingTime();

// Pass the supplier, not the value -- value is computed when get() is called
new WaitForDurationCommand(timeSupplier);

Consumer -- Callbacks

java
Consumer<XCANMotorController> motorStopper = m -> m.setPower(0);

// Apply to everything
motors.forEach(motorStopper);

// In XBot: iterate over all swerve modules
forEachSwerveModule(SwerveModuleSubsystem::refreshDataFrame);

Predicate -- Conditions

java
Predicate<XCANMotorController> isRunning = m -> m.getVelocity() > 0;

// Check if any motor is running
boolean anyRunning = motors.stream().anyMatch(isRunning);

// In XBot: soft limits
BooleanSupplier reverseLimit = () -> getPosition() < minHeight;

14. Annotations You Will See

AnnotationWhat It MeansWhere You See It
@OverrideReplacing a parent methodEvery method override
@InjectDagger provides thisConstructors, fields
@SingletonOne instance for the whole robotSubsystem classes
@TestThis method is a unit testTest files
@ModuleDagger module definitionInjection config
@ComponentDagger component (entry point)Injection config
@BindsMaps interface to implementationDagger modules
@AssistedRuntime parameter for factoryFactory classes

Annotations are metadata -- they don't change what the code does, but tools (Dagger, JUnit) read them to act on the code.


Key Terms Cheat Sheet

TermMeaningExample
GenericType parameter <T>List<XCANMotorController>
ListOrdered collectionnew ArrayList<>()
MapKey-value pairsnew HashMap<>()
OptionalMight contain a valueOptional.ofNullable(x)
LambdaAnonymous functionx -> x * 2
Method referenceShorthand lambdaSystem.out::println
StreamDeclarative data processinglist.stream().filter(...).collect(...)
EnumFixed set of constantsenum State { ON, OFF }
varType inferencevar x = new ArrayList<>();
staticBelongs to the class, not an instancepublic static final double MAX = 1.0;
RecordConcise data carrierrecord Point(double x, double y)
BuilderStep-by-step object constructionnew Builder().p(1).i(0).build()
SupplierProvides a value() -> Math.random()
ConsumerAccepts a valuex -> System.out.println(x)

Quiz

Q1: What does Optional<XCANMotorController> mean?

  • [ ] A) The motor must always exist
  • [ ] B) The motor might or might not be present
  • [ ] C) There are multiple motors
  • [ ] D) The motor is broken
Answer

B) The motor might or might not be present

Optional represents a value that may be absent. Common in XBot when hardware might not be connected. You must handle both cases -- when the value exists and when it does not.

Q2: What is the output of names.stream().filter(n -> n.startsWith("A")).collect(Collectors.toList()) if names is ["Drive", "Arm", "Intake", "Aim"]?

  • [ ] A) ["Arm", "Aim"]
  • [ ] B) ["Drive", "Intake"]
  • [ ] C) ["Aim"]
  • [ ] D) ["Arm"]
Answer

A) ["Arm", "Aim"]

filter keeps only items where the condition is true. Both "Arm" and "Aim" start with "A", so they pass the filter. "Drive" and "Intake" are removed.

Q3: Which is the correct way to safely use a value from Optional?

  • [ ] A) getOptional().get().setPower(0.5);
  • [ ] B) getOptional().ifPresent(m -> m.setPower(0.5));
  • [ ] C) getOptional().setPower(0.5);
  • [ ] D) if (getOptional() != null) getOptional().get().setPower(0.5);
Answer

B) `getOptional().ifPresent(m -> m.setPower(0.5));

ifPresent only runs the lambda if the Optional contains a value. Option A crashes if Optional is empty. Option C doesn't compile (Optional has no setPower). Option D partially works but defeats the purpose of Optional -- ifPresent is the intended pattern.

Built for XBot Robotics Team 488