Java Arena
Step 3 of 5

Writing hashCode

A hashCode for Seat

A class gets hash codes of its own the same way it gets its own equals: by writing the method. The header must be exactly public int hashCode(), with no parameters and an int back. A method with any other name, even hashcode with a small c, is just a new method, and no hash map ever calls it.

What should it return? The rule says that equal seats must get equal hash codes, and two seats are equal when their rows and numbers are. So work the hash code out from those two values, and from nothing else:

public int hashCode() {
    return 31 * this.row.hashCode() + this.number;
}
  • The row is a String, and equal Strings have equal hash codes. The number is an int, used as it is. So two equal seats give the same result, and the rule holds.
  • Why times 31? this.row.hashCode() + this.number would follow the rule too, but many different seats would share a hash code. A one-letter String's hash code is the letter's code, so B7 would be 66 + 7 = 73, and so would C6 (67 + 6) and D5 (68 + 5). With the 31, B7 is 66 * 31 + 7 = 2053, and C6 is 67 * 31 + 6 = 2083. String's own hashCode uses the same trick.
  • Use only the values that equals compares. A value that equals ignores could give two equal seats different hash codes, and that breaks the rule. (The last challenge of this step shows what happens then.)
  • If the calculation grows past the largest int and wraps around, that's fine: any int will do as a hash code.

Here's step 1's hash map program again. Only Seat has changed: it has both methods now.

Main.java
import java.util.HashMap;

public class Main {
    public static void main(String[] args) {
        HashMap<Seat, String> holders = new HashMap<>();
        Seat seat = new Seat("B", 7);
        holders.put(seat, "Ada");
        System.out.println(holders.get(seat));
        System.out.println(holders.get(new Seat("B", 7)));
        System.out.println(holders.containsKey(new Seat("B", 7)));

        holders.put(new Seat("B", 7), "Ben");
        System.out.println(holders.size());
    }
}
Seat.java
public class Seat {
    private String row;
    private int number;

    public Seat(String row, int number) {
        this.row = row;
        this.number = number;
    }

    public boolean equals(Object compared) {
        if (this == compared) {
            return true;
        }
        if (!(compared instanceof Seat)) {
            return false;
        }
        Seat other = (Seat) compared;
        return this.row.equals(other.row) && this.number == other.number;
    }

    public int hashCode() {
        return 31 * this.row.hashCode() + this.number;
    }

    public String toString() {
        return this.row + this.number;
    }
}
Output
Ada
Ada
true
1
  • A new B7 seat now has the same hash code as the key in the map, so get and containsKey look in the right bucket, and equals confirms the match.
  • put with an equal key replaces the value, as it does with String keys: the map has one B7 seat, and Ben holds it now.
  • Seat's own hash code is worked out from its values, so it's the same on every run: new Seat("B", 7).hashCode() is always 2053, and it's fine to print. Only the default one, of a class without a hashCode of its own, is never printed in this course.

Objects.hash

With more values, combining them by hand gets long. The class Objects, in java.util, has a method for it: Objects.hash(...) takes any number of values, and combines their hash codes with 31, much as String's hashCode combines its characters. Seat's hashCode could be:

public int hashCode() {
    return Objects.hash(this.row, this.number);
}
  • Seat.java then needs the line import java.util.Objects; at the top, just as a class that uses ArrayList needs its import.
  • Give it the same values that equals compares. It takes numbers as well as objects.
  • It doesn't give the same number as the hand-written version (for B7 it's 3014), and it doesn't need to: any hashCode that follows the rule works.
  • A value that's null counts as 0, where this.row.hashCode() would crash with a NullPointerException.

Letting the editor write them

Writing equals and hashCode is routine enough that most Java editors, such as IntelliJ IDEA, Eclipse and NetBeans, can generate both from the instance variables you pick, each with a line @Override above it, which module 34 explains. However they're written, write them together: Java's documentation of equals says that a class with its own equals generally needs its own hashCode too.

Your task

Challenge 1 of 3

The board game's squares get hash codes of their own. In Square.java, write hashCode: 31 times the hash code of the column, plus the row. The check lets players claim squares, and looks them up with new squares:

HashMap<Square, String> owners = new HashMap<>();
owners.put(new Square("A", 1), "Ada");
owners.put(new Square("E", 1), "Ben");
owners.put(new Square("A", 1), "Cy");
System.out.println(owners.get(new Square("A", 1)));
System.out.println(owners.containsKey(new Square("E", 2)));
System.out.println(owners.size());
System.out.println(new Square("E", 1).hashCode());

and expects:

Cy
false
2
2140

Challenge 2

The coin collector from step 1 now counts coins in a HashMap<Coin, Integer>. Coin.java has its equals. Give it a hashCode too, with Objects.hash and the same three values that equals compares. Then, in Main.java, write the method countCoins(ArrayList<Coin> coins), which returns a HashMap<Coin, Integer> from each different coin to how many times it's in the list. The check runs:

ArrayList<Coin> box = new ArrayList<>();
box.add(new Coin("Italy", 2002, 50));
box.add(new Coin("Kenya", 2005, 10));
box.add(new Coin("Italy", 2002, 50));
box.add(new Coin("Italy", 2002, 20));
HashMap<Coin, Integer> counts = countCoins(box);
System.out.println(counts.get(new Coin("Italy", 2002, 50)));
System.out.println(counts.get(new Coin("Kenya", 2005, 10)));
System.out.println(counts.size());

and expects:

2
1
3

Challenge 3

A stamp has a country, a year and a condition, such as mint or used. Stamp's equals says two stamps are equal when the country and the year match, whatever their condition. But its hashCode uses the condition too. What does this program print?