Java Arena
Step 2 of 5

Records as keys

In module 32, a class needed its own equals and hashCode before a HashMap could find its objects as keys. A record has both from the start, written by Java from the components.

Equal when the components are equal

Main.java
import java.util.HashMap;

public class Main {
    public static void main(String[] args) {
        Pin first = new Pin(3, 5);
        Pin second = new Pin(3, 5);
        System.out.println(first == second);
        System.out.println(first.equals(second));

        HashMap<Pin, String> places = new HashMap<>();
        places.put(new Pin(3, 5), "bakery");
        places.put(new Pin(8, 1), "library");
        System.out.println(places.get(new Pin(8, 1)));
        System.out.println(places.containsKey(new Pin(1, 8)));
    }
}
Pin.java
public record Pin(int x, int y) {
}
Output
false
true
library
false
  • first == second is still false: new makes two objects, and == asks whether two references point to the very same object (module 32).
  • first.equals(second) is true. A record's equals is true when the other object is a record of the same type and every component is equal: components of a primitive type such as int are compared by their value (doubles with Double.compare), and objects such as Strings with their own equals.
  • places.get(new Pin(8, 1)) finds the library, although the key in the map is another object: the map uses the record's hashCode and equals, and both look only at the components. new Pin(1, 8) has the same numbers the other way round, so it's a different key.

What hashCode gives

A record's hashCode is computed from its components, so equal records always have the same hash code, which is the rule from module 32 that a HashMap needs. Java doesn't promise what the number itself is, though: it may differ from one Java version to another. So don't print it or write code that depends on its value. The same goes for printing a whole HashMap or HashSet of records: it shows them in an order of its own (module 31). Print what you look up, or go through a list.

Records in a HashSet

A HashSet keeps each value once, by equals and hashCode, so records work in sets too. A set of pins dropped by friends who meet up, with one pin dropped twice:

Main.java
import java.util.HashSet;

public class Main {
    public static void main(String[] args) {
        HashSet<Pin> dropped = new HashSet<>();
        System.out.println(dropped.add(new Pin(3, 5)));
        System.out.println(dropped.add(new Pin(6, 2)));
        System.out.println(dropped.add(new Pin(3, 5)));
        System.out.println(dropped.size() + " different places");
    }
}
Pin.java
public record Pin(int x, int y) {
}
Output
true
true
false
2 different places

add returns false when an equal value is already in the set, and leaves the set as it was.

Your task

Challenge 1 of 3

A city's buses take travel passes, each with the holder's name and a pass number. TravelPass is a record. What does this program print?

Challenge 2

A robot vacuum notes the cell of the floor grid it's in once a minute. Cell is a record with the column x and the row y. In Main.java, write the method countVisits(ArrayList<Cell> path), which returns a HashMap<Cell, Integer> from each cell on the path to how many times it's there. The check runs:

ArrayList<Cell> path = new ArrayList<>();
path.add(new Cell(0, 0));
path.add(new Cell(0, 1));
path.add(new Cell(0, 0));
path.add(new Cell(2, 1));
path.add(new Cell(0, 0));
HashMap<Cell, Integer> visits = countVisits(path);
System.out.println(visits.size() + " different cells");
System.out.println(visits.get(new Cell(0, 0)));
System.out.println(visits.get(new Cell(2, 1)));
System.out.println(visits.getOrDefault(new Cell(1, 0), 0));

and expects:

3 different cells
3
1
0

Challenge 3

Volunteers counting birds note each sighting as the bird and the numbered square of the nature reserve where it was seen. When two volunteers note the same bird in the same square, that's one sighting. In Main.java, write the method withoutRepeats(ArrayList<Sighting> sightings), which returns a new list with each different sighting once, in the order it first came. Use a HashSet<Sighting> to remember which sightings you've already taken. The check runs:

ArrayList<Sighting> notes = new ArrayList<>();
notes.add(new Sighting("heron", 4));
notes.add(new Sighting("crane", 4));
notes.add(new Sighting("heron", 4));
notes.add(new Sighting("heron", 7));
notes.add(new Sighting("crane", 4));
ArrayList<Sighting> unique = withoutRepeats(notes);
System.out.println(unique);
System.out.println(notes.size() + " notes, " + unique.size() + " sightings");

and expects:

[Sighting[bird=heron, square=4], Sighting[bird=crane, square=4], Sighting[bird=heron, square=7]]
5 notes, 3 sightings