Methods and Mutable State
In the previous lesson, we wrote a Point class with fields, a constructor, and getters.
It can store a pair of coordinates, but it can’t do anything with them.
In this lesson, we’re going to write methods, which define a class’s behavior,
and then build a class whose state changes over time.
Writing Methods
Section titled “Writing 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 two examples of methods: length() in the String class, and the getters we wrote on Point.
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.
Let’s give Point some behavior, starting with 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, 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);
System.out.println(a.getX()); // 3.0System.out.println(b.getY()); // 2.0
Point sum = a.plus(b);System.out.println(sum.getX()); // 4.0System.out.println(sum.getY()); // 6.0System.out.println(a.norm()); // 5.0a.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.
Mutable State
Section titled “Mutable State”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.0tracker.reset();System.out.println(tracker.getPosition().getX()); // 0.0The 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.
Methods and Mutable State Exercise
Section titled “Methods and Mutable State Exercise”WIP