Gray Matter
WorkshopJava Basics
WPILib 2027 is still in alpha: these pages change as the APIs settle.
LESSON 08

Java Basics

Every example is printed on the page, so there is nothing to install and nothing to run. This covers the Java the next four lessons need, and stops there. The specimen is the small arm mechanism you build later in this workshop.

14 minutes
You’ll need
  • Nothing installed. No project, no robot, no build.
  • No framework knowledge. Mechanism and Command appear here only as type names.

You will read far more Java on this site than you write. Six pieces carry nearly all of it: the class, the field, the constructor, the method, the lambda, and the dot.

A { opens a block and the matching }closes it. The class braces hold the fields and the methods; a method's braces hold its statements.

Every statement ends in ;. A line that opens a block does not, because it is not a statement. Indentation is for you, and the compiler reads only the braces.

Classes and objects

A class describes a kind of thing. class Arm is a drawing of an arm, and a drawing moves nothing. Elsewhere a line reads new Arm(), and that builds one real arm from the drawing. What you get back is an object.

One class, any number of objects. The flywheel builds new TalonFX(21, canivore) and new TalonFX(22, canivore) from the same class and gets two separate motors.

public class Arm extends Mechanism adds the second idea. Everything Mechanism can do, Arm can do, with no trace of it in Arm.java. Search that file for runRepeatedly and you will not find it. It works anyway.

The arm object gets made in Robot.java, which writes public final Arm arm = new Arm();. The word public is what lets an OpMode reach it later as robot.arm.

Fields

Fields are what an object owns, and they last as long as the object does. The arm owns four: a bus, a motor, an encoder, and a voltage request.

Arm.java, branch mech-1-Mechanisms: the four fields
private final CANBus canivore = new CANBus("canivore");
private final TalonFX motor = new TalonFX(31, canivore);
private final CANcoder encoder = new CANcoder(32, canivore);
 
// Pushes a set voltage at the motor. No sensors involved.
private final VoltageOut voltageOut = new VoltageOut(0);

Take the second line apart, left to right.

PartWhat it means
privateOnly code inside Arm.java can touch it.
finalPoints at this one TalonFX for good. It locks the box, not the contents, so motor.setControl(...) still works.
TalonFXThe type. What kind of thing goes in the box.
motorThe name the rest of the file uses.
=Put the thing on the right into the box on the left.
new TalonFX(31, canivore)new builds the object. It is handed CAN ID 31 and the CANivore bus.

public is the opposite: any file may reach it. That is why Robot.java writes public final Arm arm.

One word carries the Commands lesson

On branch mech-1-Mechanisms the arm's setVoltage is public. One branch later it becomes private, and every request has to arrive as a command instead.

Constructors and methods

A constructor has the same name as the class and no return type in front of it. It runs once, automatically, the instant new Arm() runs. You never call it by name.

Arm.java, branch mech-1-Mechanisms: the constructor
public Arm() {
TalonFXConfiguration config =
new TalonFXConfiguration()
.withMotorOutput(
new MotorOutputConfigs()
.withNeutralMode(NeutralModeValue.Coast) // easy to move by hand
.withInverted(InvertedValue.CounterClockwise_Positive))
// Use the CANcoder for position, so the motor knows the arm's real angle.
.withFeedback(new FeedbackConfigs().withRemoteCANcoder(encoder));
 
motor.getConfigurator().apply(config);
}

Fields carrying a = new ... are built before the constructor body starts, so motor and encoder already exist when the last line hands them to Phoenix.

config is different. It is a local variable: born on that line, gone at the closing brace. setVoltage cannot see it.

Constructors can take parameters. TeleopOpMode(Robot robot) takes one Robot, and you write the driver's button bindings inside it. They get wired once, when the mode starts, not every loop.

Arm.java, branch mech-1-Mechanisms: the first method on the site
/**
* Push the arm with a fixed voltage. Positive voltage moves the arm counter-clockwise.
*
* @param voltage The voltage to apply.
*/
public void setVoltage(double voltage) {
motor.setControl(voltageOut.withOutput(voltage));
}

A signature reads left to right. public says who may call it. Next comes void, the return type. Then the name, then the parameter list: one double called voltage.

Three type words turn up constantly. A double carries a decimal point, a boolean is true or false, and void means the method hands nothing back.

Code as a value

return hands a value back to whoever called the method, and stops there. Read the return type first on any method you meet: it tells you whether you get an answer or an object.

Arm.java, branch mech-2-Commands: a method that returns a Command
// Voltages for the two example commands.
private static final double SLOW_VOLTAGE = 3.0;
 
/** Push the arm with a gentle voltage and keep pushing. Never finishes. */
public Command runSlow() {
return runRepeatedly(() -> setVoltage(SLOW_VOLTAGE)).named("runSlow (hold)");
}

runSlow() does not move the arm. It builds a Command object and hands it back, and something else runs that command later. The word return gives no hint of this. The return type does.

Inside it, () -> setVoltage(SLOW_VOLTAGE) is a lambda: a chunk of code handed over as a value. The () on the left is its input list, empty here. Everything after the arrow is the code.

WATCH OUT

Writing a lambda runs nothing

Call arm.runSlow() and setVoltage(3.0) does not happen. The lambda sits parked inside the command until the scheduler runs it. Then it runs every loop, fifty times a second, for as long as the command stays scheduled.

This failure has no error message. You call arm.runSlow(), the arm does not move, and nothing logs. Nobody ever scheduled the command.

Arm.java, branch mech-2-Commands: the stop command
/** Stop the arm motor and keep it stopped. Never finishes. */
public Command stop() {
return runRepeatedly(motor::stopMotor).named("stop (hold)");
}

motor::stopMotor means the same thing as () -> motor.stopMotor(). Two colons, and no parentheses after the name. You are handing the method over, not calling it. Write motor.stopMotor() in that slot and Java calls it on the spot, gets nothing back, and has nothing left to hand over. It will not compile.

Now the dot. Writing a.b() means "on the thing a, call b", and what you may type after a dot depends on the type in front of it. runRepeatedly(...) hands back a half-finished command, and .named(...) lives there. A finished Command has no .named(...) at all, so arm.runFast().named("lift") will not compile.

Words in passing

These appear in code you read here, and each needs one line.

WordWhat it does
staticBelongs to the class, not to an object built from it. Scheduler.getDefault() is called on the class name, with nothing constructed first.
static finalOne shared copy that never changes. That is a named constant, spelled in capitals by convention.
@OverrideThis method replaces one from the class you extended. Misspell the name and the compiler stops you.
@TeleopA label the framework reads to find your OpMode. Delete it and the mode vanishes, with no compile error. @Autonomous and @Utility are the other two.
importOne line per class borrowed from somewhere else. Your editor writes these for you.
// /** */A comment to the end of the line, and a documented block. The compiler skips both.
< > == != && || !Less than, greater than, equal, not equal, and, or, not. A leading ! reverses everything after it, so read for it deliberately.

Check your work

Nothing here runs, so the check is a reading test. Scroll back to runSlow and name every part of it out loud.

Check

You should be able to say

  • What private, static and final each do on that first line.
  • What runSlow() hands back, and what it leaves undone.
  • Where runRepeatedly comes from, given that it is not in Arm.java.
  • When setVoltage runs, and how often after that.

Miss one or two and keep going. Every construct here reappears in real code within the next three lessons.

Check yourself

01

In runRepeatedly(() -> setVoltage(SLOW_VOLTAGE)), when does setVoltage(3.0) run?

02

Why is it motor::stopMotor and not motor.stopMotor() inside runRepeatedly(...)?

03

runRepeatedly(...).named("runSlow (hold)") compiles, but arm.runFast().named("lift") does not. Why?

Pick an answer for each.
Arm.java on mech-1-Mechanisms: the file these excerpts come from