Java Arena
Step 1 of 5

equals, again

Module 23 gave classes an equals of their own, so that two objects with the same values count as equal. Module 31 used Strings and Integers as the keys of hash maps. This module brings the two together: objects of your own classes as hash map keys. That takes one more method, hashCode, and this step shows why.

A quick recap

Here's the theater seat from module 23, with its equals:

Main.java
import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        Seat mine = new Seat("B", 7);
        Seat yours = new Seat("B", 7);
        System.out.println(mine == yours);
        System.out.println(mine.equals(yours));

        ArrayList<Seat> booked = new ArrayList<>();
        booked.add(mine);
        booked.add(new Seat("C", 2));
        System.out.println(booked.contains(new Seat("C", 2)));
        System.out.println(booked.indexOf(new Seat("B", 7)));
        booked.remove(new Seat("B", 7));
        System.out.println(booked);
    }
}
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 String toString() {
        return this.row + this.number;
    }
}
Output
false
true
true
0
[C2]
  • == on two objects asks whether both variables refer to the same object. mine and yours come from two news, so they're two objects, and == says false even though their values are the same.
  • equals is Seat's own method, written the classic way: the header public boolean equals(Object compared), true for the very same object, false for anything that isn't a Seat (instanceof), and otherwise a cast to Seat and a comparison of the values. The row is a String, so it's compared with equals, and the number with ==.
  • A list's contains, indexOf and remove(value) compare with equals, so they find a brand-new Seat that's equal to one in the list.

Seats as hash map keys

A theater wants to look up who holds each seat, so it keeps a HashMap<Seat, String> from a seat to a name. It's the same Seat class, with the same equals:

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 String toString() {
        return this.row + this.number;
    }
}
Output
Ada
null
false
2
  • With the very object that was put in, get finds Ada.
  • new Seat("B", 7) is equal to that key, and a list would have found it. The map doesn't: get gives null, and containsKey gives false.
  • It gets worse. A put with a key that's already in the map should replace its value (module 31), but the new B7 seat didn't replace Ada: the map added it as a second key. Now it holds B7 twice, once for Ada and once for Ben.

A hash map doesn't go through all of its keys and compare each one with equals, the way a list's contains does: that would be slow in a big map. It first asks the key for a number, its hash code, and uses that number to decide where to look. Seat has an equals, but no hashCode of its own, so in practice the map looks in the wrong place. The next step shows what a hash code is, and step 3 gives Seat a hashCode. Until then, keep one rule in mind: a class whose objects are hash map keys needs both methods.

Your task

Challenge 1 of 3

A board game puts colored tokens on the squares of a grid, with names like A1: a column letter and a row number. Square has an equals, but no hashCode. What does this program print?

Challenge 2

A coin collector describes each coin by its country, its year and its value in cents. Two coins are equal when all three are the same. In Coin.java, write the method equals the classic way, as in the recap. The check runs:

Coin mine = new Coin("Italy", 2002, 50);
System.out.println(mine.equals(new Coin("Italy", 2002, 50)));
System.out.println(mine.equals(new Coin("Italy", 2002, 20)));
ArrayList<Coin> album = new ArrayList<>();
album.add(new Coin("Kenya", 2005, 10));
album.add(mine);
System.out.println(album.indexOf(new Coin("Italy", 2002, 50)));

and expects:

true
false
1

Challenge 3

A drawing program keeps the color of every pixel of a small picture in a list. A color is three numbers from 0 to 255: how much red, green and blue it has. In Color.java, write equals: two colors are equal when all three numbers are the same. Then, in Main.java, write the method countColors(ArrayList<Color> pixels), which returns how many different colors the picture uses. Keep a second list of the colors seen so far, and add a pixel's color to it only when an equal color isn't there yet. The check runs:

ArrayList<Color> pixels = new ArrayList<>();
pixels.add(new Color(255, 0, 0));
pixels.add(new Color(0, 0, 255));
pixels.add(new Color(255, 0, 0));
pixels.add(new Color(255, 255, 255));
pixels.add(new Color(0, 0, 255));
System.out.println(countColors(pixels));

and expects:

3