Skip to content

Classes, Fields, and Methods

In an earlier lesson, we discussed different data types in Java, such as int, double, and String. In this lesson, we’re going to learn about classes, which are a way to create our own data types, how to use them to create objects, and how to define a class’s components: fields and methods.

The Java programming language has two types of types: primitive types and reference types. Primitive types are the most basic types that are built into the language, such as int, double, and boolean. Reference types (also called object types) are more complex types that are defined by programmers. Many reference types come built into the Java Development Kit (JDK), such as String, and others come from external libraries, but you can also define your own object types.

Classes are a way to define object types. A class is a template for creating objects, with fields and methods that define the object’s state and behavior. An object is an instance of a class; for example, "a" and "b" are both objects of the String class.

Before writing any code, it helps to have a mental picture of what an object actually is. So far, all the code you’ve written has been from the perspective of a single actor: the computer running your program. For instance, System.out.println("hello"); reads like an instruction: “tell the computer to print hello.”

Classes let you write code from a different perspective, sometimes called object-oriented programming. Instead of one actor doing everything, your program is made up of many objects, each with its own state and behavior, similar to characters in a play. A character has attributes, like a costume or a name, and capabilities, like singing, or speaking a line. In code, an object’s attributes are stored in its fields, and its capabilities are defined by its methods.

Some of a character’s attributes stay the same for the whole play, like their name, while others change from scene to scene, like their costume or where they’re standing on stage. Fields work the same way: some are set once and never change, which we call immutable, while others are expected to change over time, which we call mutable. Whether a field should be mutable or immutable is a choice we make when we design a class, and we’ll see examples of both later in this lesson.

In FRC code, objects are usually more abstract than characters in a play. A Point represents a location, a Motor represents a physical motor, and so on. Objects are also just a convenient way to group related data and behavior together, so it doesn’t always have to belong to a real-world thing.

Literal Values

Primitive types are literal values that are directly stored in memory. For example, 123 is a literal value of type int.

The String type is special, because it is a reference type, but it can be created using a literal value, such as "Hello, world!". For example, "Hello, world!" is a reference value of type String. This means you can access its methods, such as length(). For example, "Hello, world!".length() returns 13.

Now that we know what a class is, let’s define our own class. Our first example will be a simple Point class that represents a point in a two-dimensional space. This will help us write code that lets our robot know where on the field it is.

In Java, every class is defined in a .java file, and starts with the public class keywords. The public keyword means the class can be accessed from anywhere in the program, including other files. We’ll see the public keyword again later in this lesson, applied to fields and methods instead of a whole class. The class keyword tells Java we’re defining a new class, followed by the name we’re giving it, Point.

Let’s create a new file called Point.java and start it like this:

public class Point {
}

Then, we’re going to add three fields. A field is a variable that belongs to a class, and stores part of an object’s state. Every Point object will have its own copy of each field, so two different Point objects can have different coordinates. Firstly, we add two double fields, x and y, which represent the point’s coordinates; every Point object can have a different value for x and y.

// Each Point has its own x and y coordinates, and they never change.
private final double x;
private final double y;

Notice that we used the final keyword to make these fields immutable. This means that once they are created, they cannot be changed. Immutable fields are also called constant fields. A general rule of thumb is to make all fields final unless you have a good reason not to, which we’ll see later in this lesson. We also use the private keyword to make these fields private. This means that they can only be accessed by methods in the same class.

So far, the x and y fields each belong to a specific Point object. Sometimes, though, we want a value that’s shared by every object of a class, instead of one copy per object. Next, we’re going to add a static constant field, ORIGIN, which represents the point at the origin (0, 0).

// Shared by every Point, instead of belonging to just one instance.
public static final Point ORIGIN = new Point(0, 0);

The static keyword is used to make a field static. A static field is a field shared by all instances of a class. In this case, the ORIGIN field is shared by all Point objects, and it represents the point at the origin (0, 0). Static fields and methods are accessed using the class name, instead of an instance of the class, so the ORIGIN field is accessed using Point.ORIGIN. The ORIGIN field is also public, so it can be accessed from anywhere in the program.

Static vs. Instance

Non-static fields and methods are called instance fields and instance methods, respectively, to distinguish them from static fields and methods.

A method is a block of code that is defined inside a class. Methods are used to define the behavior of the class. We already saw one example of a method, which is length() in the String class. Methods can have parameters, which are values that are passed into the method when it is called.

When a method finishes, it can give a value back to whoever called it; this is called returning a value. To return a value, a method’s body uses the return keyword, followed by the value to return. As soon as a return statement runs, the method stops executing immediately and gives that value back to the caller. For example, the length() method in the String class returns the length of the string, and has no parameters.

The general syntax for a method is:

public ReturnType methodName(ParameterType1 param1, ParameterType2 param2, ...) {
// method body
}

A method can have any number of parameters, including none, and the parameters can be of any type. ReturnType is the type of the value the method returns, such as int for length(). A method doesn’t have to return something; if it doesn’t return anything, we use the return type void, which is a keyword that indicates that the method doesn’t return anything. A void method can still use return on its own, without a value, to stop early, but it’s more commonly left out entirely, since the method just finishes on its own once it reaches the end.

A constructor is a special method that is called when an object is created with the new keyword. It is often used to initialize the object’s fields.

To define a constructor, we use the public keyword, followed by the class name, and then a parameter list in parentheses.

// Sets this Point's coordinates to the given x and y values.
public Point(double x, double y) {
this.x = x;
this.y = y;
}

This allows us to create new Point objects with the new keyword. For example, the point (1, 1) can be created with new Point(1, 1); new Point (1, 1) is an object of the Point class.

You’ll notice that we use the this keyword. The this keyword is used to refer to the current object. It is used to access the object’s fields and methods. In this example, we use the this keyword to access the x and y fields of the current object, and set them to the values of the parameters x and y. Using this is necessary here because the parameter names are the same as the field names, so we use this.x to refer to the field x, and x to refer to the parameter x. this isn’t always required: if a method’s parameters have different names than the fields they set, Java can already tell them apart, and you can leave this out.

Next we’re going to define some getter methods. Getter methods are methods that simply get, or return, the value of a field. They have the same return type as the field they return, and no parameters. Because the x and y fields are private, we need to define getter methods to allow other classes to access them:

// Let other classes read the private x and y fields.
public double getX() { return this.x; }
public double getY() { return this.y; }

As you can see, getter methods conventionally start with get, followed by the name of the field.

Next, we’re going to define the plus method. The plus method takes another Point object as a parameter, and returns a new Point object that is the sum of the two Point objects.

// Adds this Point's coordinates to another Point's coordinates.
public Point plus(Point other) {
return new Point(this.x + other.x, this.y + other.y);
}

Here, other is just the name we chose for the method’s parameter, the other Point we’re adding. this still refers to the Point that plus was called on, same as in the constructor. So this.x and this.y are the coordinates of the Point we called plus on, and other.x and other.y are the coordinates of the Point passed in.

In this method, we use the new keyword to create a new Point object. Because the constructor we defined earlier takes two parameters, we pass this.x + other.x and this.y + other.y as the arguments to the constructor. Therefore, the new Point object will have the sum of the two Point objects’ coordinates.

The minus method works the same way, but subtracts the coordinates instead of adding them, giving us the vector that points from other to this Point:

// Subtracts another Point's coordinates from this Point's coordinates.
public Point minus(Point other) {
return new Point(this.x - other.x, this.y - other.y);
}

Finally, we’re going to define a norm method that returns the distance of this object from the origin.

// Computes this Point's distance from the origin.
public double norm() {
double sumOfSquares = this.x * this.x + this.y * this.y;
return Math.sqrt(sumOfSquares);
}

This method has a local variable, sumOfSquares, that stores the sum of the squares of the x and y fields. Local variables are variables that are only visible inside the method, and are used to store intermediate values. This method also uses the Math.sqrt() method, which is a static method in the Math class, which returns the square root of its argument.

The Math Class

The Math class, built into the JDK, is a collection of static methods and constants for common mathematical operations, such as Math.sqrt(), Math.abs(), and Math.pow(), as well as constants like Math.PI. Because all of these methods and constants are static, we access them using only the name of the class, like when we called Math.sqrt() in the Point.norm() method.

Here is the complete Point class:

class Point {
// Each Point has its own x and y coordinates, and they never change.
private final double x;
private final double y;
// Shared by every Point, instead of belonging to just one instance.
public static final Point ORIGIN = new Point(0, 0);
// Sets this Point's coordinates to the given x and y values.
public Point(double x, double y) {
this.x = x;
this.y = y;
}
// Let other classes read the private x and y fields.
public double getX() { return this.x; }
public double getY() { return this.y; }
// Adds this Point's coordinates to another Point's coordinates.
public Point plus(Point other) {
return new Point(this.x + other.x, this.y + other.y);
}
// Subtracts another Point's coordinates from this Point's coordinates.
public Point minus(Point other) {
return new Point(this.x - other.x, this.y - other.y);
}
// Computes this Point's distance from the origin.
public double norm() {
double sumOfSquares = this.x * this.x + this.y * this.y;
return Math.sqrt(sumOfSquares);
}
}

And here is an example of how to use it:

Point a = new Point(3, 4);
Point b = new Point(1, 2);
Point sum = a.plus(b);
System.out.println(sum.getX()); // 4.0
System.out.println(sum.getY()); // 6.0
System.out.println(a.norm()); // 5.0

We create new Point objects using the new keyword followed by the constructor. We then call methods on them using the dot operator (.): a.plus(b) means “call the plus method on the object a, passing b as the argument.” Inside plus, a becomes this and b becomes other. The same pattern applies to any method call: object.method(arguments).

We can also access the ORIGIN constant directly on the class, without creating an instance:

System.out.println(Point.ORIGIN.getX()); // 0.0

The Point class we defined is immutable, meaning that once a Point is created, its x and y values can never change, because the fields are final. Immutability is generally a good thing: it makes classes easier to reason about, since you never have to worry about a value changing unexpectedly.

Sometimes, however, we need a class whose state changes over time. Let’s define a RobotTracker class that tracks a robot’s current position on the field. The position starts somewhere and is updated as the robot moves, so it cannot be final:

// Not final, since the robot's position changes as it moves.
private Point position;

Notice that a field’s type doesn’t have to be a primitive type like double. Just like x and y were double fields, position is a field whose type is our own Point class. Any type, whether built into Java or one we defined ourselves, can be used as a field type.

We still use private to prevent other classes from directly modifying the field, so the only way to change the position is through the methods we define. This matters because move, distanceTo, and reset can rely on position always being valid, without worrying about another class setting it to something unexpected; if outside code could reach in and overwrite position directly, RobotTracker couldn’t guarantee its own behavior.

The constructor works the same as before, initializing position to a given starting value:

// Starts tracking from a given position.
public RobotTracker(Point startPosition) {
this.position = startPosition;
}

A class can have more than one constructor, as long as each one takes a different set of parameters. This is useful when there’s a sensible default: here, a RobotTracker with no arguments starts at the origin. Instead of repeating this.position = Point.ORIGIN, we use this(...) to call the other constructor:

// Starts tracking from the origin by default.
public RobotTracker() {
this(Point.ORIGIN);
}

When calling move, you pass in a delta, the change in position, and the method adds it to the current position. This is mutation, as the method reassigns this.position, changing the object’s state:

// Moves the tracked position by delta.
public void move(Point delta) {
this.position = this.position.plus(delta);
}

Notice how the move method has a return type of void. This is because the method doesn’t need to actually return anything. It instead changes its own internal state.

distanceTo computes the straight-line distance from the current position to a target. It uses a local variable diff to hold the vector between the two points before taking its length:

// Finds the straight-line distance from the current position to target.
public double distanceTo(Point target) {
Point diff = target.minus(this.position);
return diff.norm();
}

Because position is private, we also need a getter method to read the robot’s current position:

// Lets other classes read the current position.
public Point getPosition() {
return this.position;
}

reset sets the position back to the origin. Instead of writing new Point(0, 0), we reuse the Point.ORIGIN constant. Both would work, since a new Point(0, 0) has the same coordinates as Point.ORIGIN, but reusing ORIGIN avoids creating a redundant object and makes the code’s intent clearer: we specifically mean the origin, not just some point that happens to be at (0, 0):

// Moves the tracked position back to the origin.
public void reset() {
this.position = Point.ORIGIN;
}

Here is the complete RobotTracker class:

class RobotTracker {
// Not final, since the robot's position changes as it moves.
private Point position;
// Starts tracking from a given position.
public RobotTracker(Point startPosition) {
this.position = startPosition;
}
// Starts tracking from the origin by default.
public RobotTracker() {
this(Point.ORIGIN);
}
// Moves the tracked position by delta.
public void move(Point delta) {
this.position = this.position.plus(delta);
}
// Finds the straight-line distance from the current position to target.
public double distanceTo(Point target) {
Point diff = target.minus(this.position);
return diff.norm();
}
// Lets other classes read the current position.
public Point getPosition() {
return this.position;
}
// Moves the tracked position back to the origin.
public void reset() {
this.position = Point.ORIGIN;
}
}

And an example of using it:

RobotTracker tracker = new RobotTracker(Point.ORIGIN);
tracker.move(new Point(3, 0));
tracker.move(new Point(0, 4));
System.out.println(tracker.distanceTo(Point.ORIGIN)); // 5.0
tracker.reset();
System.out.println(tracker.getPosition().getX()); // 0.0
Note

The Point class we created is a simplified version of WPILib’s Translation2d class, which has many more methods that are often used in robot projects.

The RobotTracker class we created is the start of the concept of localization, which is the process of determining a robot’s position on the field. We’re going to explore this concept in much more detail in later stages of the course.

Exercise

WIP