Unit 4 · Lesson 1

Classes and Objects

You have been using objects since Unit 2. Every TalonFX, every XboxController, every SwerveModule is an object — an instance of a class. This lesson explains what that means, why Java is built around this model, and how to define your own classes for robot subsystems.

By the end of this lesson, you will:

  • Define the relationship between a class (blueprint) and an object (instance)
  • Identify the four members a class can contain: fields, constructors, methods, and nested types
  • Declare a simple class with fields and methods and instantiate it with new
  • Explain what an object reference variable holds and why two reference variables can point to the same object
  • Recognize the class structure of a WPILib subsystem and map it to the concepts in this lesson

Everything Is an Object

Before you wrote a single class yourself, you were already working inside an object-oriented system. new TalonFX(0) calls the TalonFX class constructor and creates one motor controller object. new XboxController(0) creates a joystick object. driverController.getLeftY() calls a method on that object. The entire WPILib framework is a collection of classes — and your Robot.java file defines a class that extends one of them.

A class is a blueprint — a definition of what a thing is, what data it holds, and what it can do. An object is a live instance of that blueprint in memory: real data, real state, real behavior. Java is object-oriented because almost everything is modeled as an object with state and behavior bundled together.

The Four Members of a Class

Every Java class can contain four kinds of members. These are the same building blocks you'll find in every WPILib class, every vendor library, and every subsystem you write this season. Click each card to see how it appears in FRC code.

👇 Click each card to see the FRC context
📦
Fields
tap for context
Fields (instance variables)

Data the object holds. In a SwerveModule class: the drive motor, steer motor, encoder, and current angle are all fields. Each object instance gets its own copy.

🔧
Constructors
tap for context
Constructors

Special methods that run when new is called. new TalonFX(0) calls the TalonFX constructor with CAN ID 0. Covered fully in Lesson 2.

Methods
tap for context
Methods

Actions the object can perform or calculations it can return. motor.set(0.5), encoder.getPosition(), and isAtTarget() are all method calls on objects.

🔒
Access Modifiers
tap for context
Access Modifiers

public, private, protected — control who can see a field or method. private hides the motor object inside the subsystem; public exposes the speed-setting method. Covered fully in Lesson 4.

Defining a Class

Here is the minimal structure of a Java class. Every subsystem you write this season will follow this skeleton:

// Class declaration — "public" means other classes can use it public class SwerveModule { // ── Fields: data this object holds ──────────────────────── private final TalonFX driveMotor; private final TalonFX steerMotor; private final CANcoder steerEncoder; private double currentAngleDeg = 0.0; private boolean isCalibrated = false; // ── Constructor: runs when "new SwerveModule(...)" is called public SwerveModule(int driveId, int steerId, int encoderId) { driveMotor = new TalonFX(driveId); steerMotor = new TalonFX(steerId); steerEncoder = new CANcoder(encoderId); } // ── Methods: what this object can do ────────────────────── public void setDesiredState(double speedMps, double angleDeg) { driveMotor.set(speedMps / MAX_SPEED_MPS); steerToAngle(angleDeg); } public double getCurrentAngle() { return steerEncoder.getAbsolutePosition().getValueAsDouble() * 360.0; } private void steerToAngle(double targetDeg) { currentAngleDeg = targetDeg; // simplified } }

Instantiating Objects: the new Keyword

A class definition does nothing on its own — it's a blueprint sitting on a shelf. The new keyword activates that blueprint, allocates memory, runs the constructor, and hands back a reference to the live object. Each call to new creates an independent object with its own copy of every instance field.

Use the Object Factory below to see this in action. Each click instantiates a new SwerveModule with different CAN IDs — each gets its own driveMotor, steerMotor, and steerEncoder.

Object Factory — SwerveModule
public class SwerveModule {
  private final TalonFX driveMotor;
  private final TalonFX steerMotor;
  private final CANcoder encoder;
  private double angleDeg = 0.0;
  // ... constructor + methods
}
No objects yet — click the button to instantiate
Ready. A robot drivetrain typically has 4 SwerveModule instances — one per corner.

Object references

When you write SwerveModule fl = new SwerveModule(0, 1, 2);, the variable fl doesn't hold the entire object — it holds a reference to the object in memory. This has one important consequence: if you assign the same object to two variables, both variables point to the same object. Changing the object through one variable changes it for the other.

// fl and sameModule both point to the SAME object in memory SwerveModule fl = new SwerveModule(0, 1, 2); SwerveModule sameModule = fl; sameModule.setDesiredState(1.0, 45.0); // fl.getCurrentAngle() also returns 45.0 — same object // Contrast: two SEPARATE objects with their own state SwerveModule fl = new SwerveModule(0, 1, 2); SwerveModule fr = new SwerveModule(3, 4, 5); // fl and fr have independent copies of all fields

A WPILib Subsystem Is Just a Class

Everything you've seen in WPILib is built on this model. TimedRobot is a class. Your Robot.java extends it. When WPILib's main loop calls teleopPeriodic(), it's calling a method on your Robot object. In Unit 6 you'll write SubsystemBase subclasses — each one is a class that models one mechanism on the robot, with fields for that mechanism's hardware and methods for its behaviors.

The class definition lives in a .java file. It describes what every instance of this class will look like — fields, constructor, methods. Writing the class doesn't create any objects.

// IntakeSubsystem.java — the blueprint public class IntakeSubsystem { private final TalonFX intakeMotor; private final DigitalInput beamBreak; private boolean gamePieceHeld = false; public IntakeSubsystem() { intakeMotor = new TalonFX(INTAKE_MOTOR_ID); beamBreak = new DigitalInput(BEAM_BREAK_PORT); } public void run() { intakeMotor.set(INTAKE_SPEED); } public void stop() { intakeMotor.set(0.0); } public boolean hasGamePiece() { return beamBreak.get(); } }

new IntakeSubsystem() runs the constructor — allocates memory, creates the TalonFX and DigitalInput objects inside it, and hands back a reference. This typically happens in robotInit() or in a containing class.

// In Robot.java or RobotContainer.java private final IntakeSubsystem intake = new IntakeSubsystem(); // One object, one set of motors and sensors, one state. // The entire robot shares this single IntakeSubsystem instance.

Methods on the object are called through the reference variable using dot notation. The intake variable knows which object it points to; Java routes the method call to that specific object's state and behavior.

// In teleopPeriodic() if (driverController.getAButton()) { intake.run(); } if (intake.hasGamePiece()) { intake.stop(); SmartDashboard.putBoolean("Game Piece", true); }
🔍 LRI Observation

During robot inspections, I look for teams that have a single monolithic Robot.java file with hundreds of lines of teleop logic versus teams that have a clean Robot.java that instantiates and delegates to subsystem classes. The second pattern doesn't just look better — it's faster to debug, easier to hand to a new programmer, and produces fewer mysterious interactions between mechanisms. The class boundary forces the programmer to think about what belongs to what. That clarity shows up on the competition field.

🔌 System Check

⚙️ Classes, Objects, and Robot Architecture
  • One class per mechanism. Each physical subsystem — drivetrain, intake, arm, climber — should be its own class. The class boundary defines what code belongs to that mechanism and nothing else.
  • Hardware objects are instance fields. Motor controllers, encoders, and sensors live as private final fields of their subsystem class. They should not be accessible from outside the class.
  • One instance of each subsystem. The robot has one physical intake, so there should be one IntakeSubsystem object. Creating multiple instances of a subsystem means multiple objects trying to control the same hardware.
  • Fields initialized in the constructor. Hardware objects that require CAN IDs or port numbers should be created in the constructor, not as field initializers, so the configuration is explicit and parameterizable.

Knowledge Check

You write TalonFX motor = new TalonFX(5);. What does the variable motor hold?
  • 1A copy of all the TalonFX object's data
  • 2A reference (memory address) to a TalonFX object that was created in heap memory
  • 3The integer 5 — the CAN ID passed to the constructor
  • 4The TalonFX class definition itself
A robot has two SwerveModule objects: fl (front-left) and fr (front-right). A programmer runs fr = fl;. What happens next time they call fr.setDesiredState(…)?
  • 1The front-right module is commanded — fr still points to its own separate object
  • 2The front-left module is commanded — fr = fl made both variables point to the same object; the original front-right object is now unreachable and orphaned in memory
  • 3Both modules are commanded simultaneously
  • 4A compile error — you cannot assign one SwerveModule to another
Which statement correctly describes the relationship between the TalonFX class and the four TalonFX objects in a swerve drivetrain?
  • 1Each motor has its own unique version of the TalonFX class compiled separately
  • 2The TalonFX class is destroyed after each object is created
  • 3One TalonFX class definition (blueprint) exists; four independent TalonFX objects (instances) are created from it, each with its own state including its own CAN ID and velocity reading
  • 4All four TalonFX objects share the same fields because they come from the same class
💪 Practice Prompt

Design and Build Your First Subsystem Class

  1. Without writing code yet: list the fields, constructor parameters, and public methods you would expect a ClimberSubsystem class to have. Think physically — what does the climber need to remember? What can it do?
  2. Write the class in WPILib VS Code. Give it at least two private final hardware fields, a constructor that initializes them with CAN IDs from Constants.java, and three public methods: extend(), retract(), and isAtLimit().
  3. In a separate file (or your existing Robot.java), instantiate one ClimberSubsystem as a field, and call its methods from teleopPeriodic() based on button inputs.
  4. Try instantiating two ClimberSubsystem objects with the same CAN IDs. What happens when you deploy? Why?
  5. Bonus: Look at Team 2910's most recent public robot repository. Find two subsystem classes. For each one, list: the instance fields, the constructor parameters, and the public methods. How does their structure compare to the pattern in this lesson?