{"id":"equals-hashcode-objects-as-keys","slug":"objects-as-keys","title":"Objects as keys in practice","text":"Everything is in place now: a key class with `equals` and `hashCode`, and a `HashMap` that uses it. This step puts them to work in a small program, and deals with one more thing: the order in which to print what's in the map.\n\n### A game board\n\nA board game is played on a grid of squares, with columns A to F and rows 1 to 6, and each player has a token of a color. The game keeps the tokens in a `HashMap<Square, String>`, from a square to the color of the token on it. Square is the class from steps 1 and 3, now with both methods. The class `Board` places tokens, looks up squares, moves tokens and prints them all:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        Board board = new Board();\n        board.place(new Square(\"C\", 2), \"red\");\n        board.place(new Square(\"A\", 1), \"blue\");\n        board.place(new Square(\"B\", 3), \"green\");\n        board.move(new Square(\"B\", 3), new Square(\"D\", 4));\n        System.out.println(board.tokenAt(new Square(\"D\", 4)));\n        System.out.println(board.tokenAt(new Square(\"B\", 3)));\n        board.printTokens();\n    }\n}\n// ==== Board.java ====\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\n\npublic class Board {\n    private HashMap<Square, String> tokens;\n\n    public Board() {\n        this.tokens = new HashMap<>();\n    }\n\n    public void place(Square square, String color) {\n        this.tokens.put(square, color);\n    }\n\n    public String tokenAt(Square square) {\n        if (this.tokens.containsKey(square)) {\n            return this.tokens.get(square);\n        }\n        return \"empty\";\n    }\n\n    public void move(Square from, Square to) {\n        if (this.tokens.containsKey(from)) {\n            String color = this.tokens.get(from);\n            this.tokens.remove(from);\n            this.tokens.put(to, color);\n        }\n    }\n\n    public void printTokens() {\n        ArrayList<String> lines = new ArrayList<>();\n        for (Square square : this.tokens.keySet()) {\n            lines.add(square + \" \" + this.tokens.get(square));\n        }\n        Collections.sort(lines);\n        for (String line : lines) {\n            System.out.println(line);\n        }\n    }\n}\n// ==== Square.java ====\npublic class Square {\n    private String column;\n    private int row;\n\n    public Square(String column, int row) {\n        this.column = column;\n        this.row = row;\n    }\n\n    public boolean equals(Object compared) {\n        if (this == compared) {\n            return true;\n        }\n        if (!(compared instanceof Square)) {\n            return false;\n        }\n        Square other = (Square) compared;\n        return this.column.equals(other.column) && this.row == other.row;\n    }\n\n    public int hashCode() {\n        return 31 * this.column.hashCode() + this.row;\n    }\n\n    public String toString() {\n        return this.column + this.row;\n    }\n}\n```\n\n```output\ngreen\nempty\nA1 blue\nC2 red\nD4 green\n```\n\n- Every method is given a Square made with `new`, never the object that was put in. `containsKey`, `get`, `put` and `remove` still find the right key, because Square has both `equals` and `hashCode`.\n- `remove(key)` takes a key and its value out of the map (module 31). `move` gets the color, removes the old square, and puts the token on the new one.\n- `tokenAt` checks `containsKey` first, so an empty square gives `empty` instead of `null`.\n\n### The order of the keys\n\n`printTokens` goes through the keys with `keySet()`, as in module 31. A hash map goes through its keys in an order set by their hash codes, not in the order they were added, and it's no order to rely on. With keys of your own class, it depends on how `hashCode` is written. So `printTokens` makes its own order: it builds a line for each token, puts the lines in a list, and sorts the list with `Collections.sort` (module 28). Square names sort well as Strings on this board: the column's letter first, then the row, which is always one digit.\n\nTwo other ways to get an order you can count on:\n\n- Keep the keys in an `ArrayList` too, next to the map, in the order they were added. Go through the list, and get each value from the map.\n- Go through the keys you want in the order you want, such as the squares of one row from A to F, and look each one up with a new Square and `containsKey` or `get`.\n\nOne warning: never go through, or print, a hash map whose keys have no `hashCode` of their own. Their hash codes are Java's default ones, which can change from run to run, and the order would change with them.\n\n### Keys that don't change\n\nA key goes into the bucket of the hash code it has when it's put in. If the key object changed later, its hash code would change too, and the map would look for it in the wrong bucket. So key classes usually have no setters: none of the key classes in this module can be changed once an object is made. That's why `move` doesn't change a Square: it removes the old key and puts in a new one.\n","task":"A theater keeps its bookings in a `HashMap<Seat, String>`, from a seat to the name it's booked for, and the box office wants to see one row at a time. `Seat.java` is ready, with `equals`, `hashCode` and `toString`. Write the class `Bookings`, with:\n\n- a constructor without parameters, which creates the map\n- `book(Seat seat, String name)`, which books the seat for the name (booking a seat again replaces the name)\n- `printRow(String row, int seats)`, which prints the seats of one row, from number 1 up to `seats`, one per line: the seat, a colon, and the name it's booked for, or `free`\n\n`printRow` can't go through the map's keys: they come in the map's own order, and a free seat isn't a key at all. Go through the seat numbers in order instead, make each seat with `new Seat(row, number)`, and look it up. The check runs:\n\n```java\nBookings bookings = new Bookings();\nbookings.book(new Seat(\"B\", 2), \"Ada\");\nbookings.book(new Seat(\"B\", 3), \"Ben\");\nbookings.book(new Seat(\"C\", 1), \"Cy\");\nbookings.printRow(\"B\", 4);\n```\n\nand expects:\n\n```\nB1: free\nB2: Ada\nB3: Ben\nB4: free\n```\n","kind":"code","seed":"// ==== Bookings.java ====\nimport java.util.HashMap;\n\npublic class Bookings {\n\n}\n\n// ==== Seat.java ====\nimport java.util.Objects;\n\npublic class Seat {\n    private String row;\n    private int number;\n\n    public Seat(String row, int number) {\n        this.row = row;\n        this.number = number;\n    }\n\n    public boolean equals(Object compared) {\n        if (this == compared) {\n            return true;\n        }\n        if (!(compared instanceof Seat)) {\n            return false;\n        }\n        Seat other = (Seat) compared;\n        return this.row.equals(other.row) && this.number == other.number;\n    }\n\n    public int hashCode() {\n        return Objects.hash(this.row, this.number);\n    }\n\n    public String toString() {\n        return this.row + this.number;\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        Bookings bookings = new Bookings();\n        bookings.book(new Seat(\"B\", 2), \"Ada\");\n        bookings.book(new Seat(\"B\", 3), \"Ben\");\n        bookings.book(new Seat(\"C\", 1), \"Cy\");\n        bookings.printRow(\"B\", 4);\n    }\n}\n","solution":"// ==== Bookings.java ====\nimport java.util.HashMap;\n\npublic class Bookings {\n    private HashMap<Seat, String> holders;\n\n    public Bookings() {\n        this.holders = new HashMap<>();\n    }\n\n    public void book(Seat seat, String name) {\n        this.holders.put(seat, name);\n    }\n\n    public void printRow(String row, int seats) {\n        for (int number = 1; number <= seats; number++) {\n            Seat seat = new Seat(row, number);\n            System.out.println(seat + \": \" + this.holders.getOrDefault(seat, \"free\"));\n        }\n    }\n}\n\n// ==== Seat.java ====\nimport java.util.Objects;\n\npublic class Seat {\n    private String row;\n    private int number;\n\n    public Seat(String row, int number) {\n        this.row = row;\n        this.number = number;\n    }\n\n    public boolean equals(Object compared) {\n        if (this == compared) {\n            return true;\n        }\n        if (!(compared instanceof Seat)) {\n            return false;\n        }\n        Seat other = (Seat) compared;\n        return this.row.equals(other.row) && this.number == other.number;\n    }\n\n    public int hashCode() {\n        return Objects.hash(this.row, this.number);\n    }\n\n    public String toString() {\n        return this.row + this.number;\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        Bookings bookings = new Bookings();\n        bookings.book(new Seat(\"B\", 2), \"Ada\");\n        bookings.book(new Seat(\"B\", 3), \"Ben\");\n        bookings.book(new Seat(\"C\", 1), \"Cy\");\n        bookings.printRow(\"B\", 4);\n    }\n}\n","hints":["Bookings has one instance variable, `private HashMap<Seat, String> holders;`, and the constructor creates it with `new HashMap<>()`. `book` is one `put`: Seat has `equals` and `hashCode`, so booking an equal seat again replaces the name.","In `printRow`, loop `for (int number = 1; number <= seats; number++)`, make `Seat seat = new Seat(row, number);`, and print `seat + \": \" + this.holders.getOrDefault(seat, \"free\")`."],"tests":[{"name":"Test 1","stdin":"","call":"Bookings bookings = new Bookings();\nbookings.book(new Seat(\"B\", 2), \"Ada\");\nbookings.book(new Seat(\"B\", 3), \"Ben\");\nbookings.book(new Seat(\"C\", 1), \"Cy\");\nbookings.printRow(\"B\", 4);\n","expect":"B1: free\nB2: Ada\nB3: Ben\nB4: free","hidden":false},{"name":"Test 2","stdin":"","call":"Bookings bookings = new Bookings();\nbookings.book(new Seat(\"A\", 1), \"Dee\");\nbookings.book(new Seat(\"A\", 1), \"Eve\");\nbookings.book(new Seat(\"A\", 3), \"Finn\");\nbookings.book(new Seat(\"B\", 1), \"Gus\");\nString row = \"A\";\nbookings.printRow(row, 3);\nbookings.printRow(\"C\", 2);\n","expect":"A1: Eve\nA2: free\nA3: Finn\nC1: free\nC2: free","hidden":true},{"name":"Test 3","stdin":"","call":"Bookings bookings = new Bookings();\nbookings.book(new Seat(\"D\", 12), \"Hal\");\nbookings.book(new Seat(\"D\", 9), \"Ivy\");\nbookings.printRow(\"D\", 0);\nbookings.printRow(\"D\", 12);\n","expect":"D1: free\nD2: free\nD3: free\nD4: free\nD5: free\nD6: free\nD7: free\nD8: free\nD9: Ivy\nD10: free\nD11: free\nD12: Hal","hidden":true}],"require":[{"pattern":"HashMap<Seat,\\s*String>","message":"Keep the bookings in a `HashMap<Seat, String>`."}],"forbid":[{"pattern":"\\.keySet\\s*\\(","message":"Go through the seat numbers of the row in order, not through the map's keys: they come in the map's own order, and a free seat isn't a key."}],"more":[{"task":"Here `Coin`'s `hashCode` uses only the country, so every Italian coin has the same hash code. Does the map still work? What does this program print?\n","kind":"predict","seed":"import java.util.HashMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        HashMap<Coin, String> places = new HashMap<>();\n        places.put(new Coin(\"Italy\", 2002, 50), \"box 1\");\n        places.put(new Coin(\"Italy\", 1999, 20), \"box 2\");\n        places.put(new Coin(\"Italy\", 2002, 50), \"box 3\");\n        places.remove(new Coin(\"Italy\", 1999, 20));\n\n        System.out.println(places.size());\n        System.out.println(places.get(new Coin(\"Italy\", 2002, 50)));\n        System.out.println(places.containsKey(new Coin(\"Italy\", 1999, 20)));\n        System.out.println(places.get(new Coin(\"Italy\", 2002, 20)));\n    }\n}\n// ==== Coin.java ====\npublic class Coin {\n    private String country;\n    private int year;\n    private int cents;\n\n    public Coin(String country, int year, int cents) {\n        this.country = country;\n        this.year = year;\n        this.cents = cents;\n    }\n\n    public boolean equals(Object compared) {\n        if (this == compared) {\n            return true;\n        }\n        if (!(compared instanceof Coin)) {\n            return false;\n        }\n        Coin other = (Coin) compared;\n        return this.country.equals(other.country) && this.year == other.year && this.cents == other.cents;\n    }\n\n    public int hashCode() {\n        return this.country.hashCode();\n    }\n}\n","solution":"import java.util.HashMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        HashMap<Coin, String> places = new HashMap<>();\n        places.put(new Coin(\"Italy\", 2002, 50), \"box 1\");\n        places.put(new Coin(\"Italy\", 1999, 20), \"box 2\");\n        places.put(new Coin(\"Italy\", 2002, 50), \"box 3\");\n        places.remove(new Coin(\"Italy\", 1999, 20));\n\n        System.out.println(places.size());\n        System.out.println(places.get(new Coin(\"Italy\", 2002, 50)));\n        System.out.println(places.containsKey(new Coin(\"Italy\", 1999, 20)));\n        System.out.println(places.get(new Coin(\"Italy\", 2002, 20)));\n    }\n}\n// ==== Coin.java ====\npublic class Coin {\n    private String country;\n    private int year;\n    private int cents;\n\n    public Coin(String country, int year, int cents) {\n        this.country = country;\n        this.year = year;\n        this.cents = cents;\n    }\n\n    public boolean equals(Object compared) {\n        if (this == compared) {\n            return true;\n        }\n        if (!(compared instanceof Coin)) {\n            return false;\n        }\n        Coin other = (Coin) compared;\n        return this.country.equals(other.country) && this.year == other.year && this.cents == other.cents;\n    }\n\n    public int hashCode() {\n        return this.country.hashCode();\n    }\n}\n","hints":["The rule only asks equal coins to have equal hash codes, and equal coins have the same country. So this `hashCode` follows it: the Italian coins all share one bucket, and `equals` tells them apart there.","The third `put` replaces box 1 with box 3, and `remove` takes the 1999 coin out. No 2002 coin worth 20 cents was ever put in."],"tests":[{"name":"Output","stdin":"","expect":"1\nbox 3\nfalse\nnull","hidden":false}],"require":[],"forbid":[],"lines":["1","box 3","false","null"]},{"task":"The board game from the lesson wants to know which squares get the most traffic. Read the squares that tokens land on, such as `C4`, one per line, until an empty line, and count the landings on each square in a `HashMap<Square, Integer>`. After the empty line comes one more line: the number of a row. Print that row's squares from column A to F, one per line, each with how many times a token landed there, like `C4: 2`. A square nobody landed on gets 0.\n\n`Square.java` has the column, the row and a `toString` that gives names like `C4`: write its `equals` and `hashCode`. In `Main.java`, a line such as `C4` gives the column with `line.substring(0, 1)` and the row with `Integer.valueOf(line.substring(1))`. For the output, don't go through the map: go through the columns A to F (an array `{\"A\", \"B\", \"C\", \"D\", \"E\", \"F\"}` works well) and look each square up with a new Square. For the input shown, the program prints:\n\n```\nA4: 0\nB4: 1\nC4: 2\nD4: 0\nE4: 0\nF4: 1\n```\n","kind":"code","seed":"// ==== Square.java ====\npublic class Square {\n    private String column;\n    private int row;\n\n    public Square(String column, int row) {\n        this.column = column;\n        this.row = row;\n    }\n\n    // Write the equals and hashCode methods here.\n\n    public String toString() {\n        return this.column + this.row;\n    }\n}\n\n// ==== Main.java ====\nimport java.util.HashMap;\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        HashMap<Square, Integer> landings = new HashMap<>();\n\n    }\n}\n","solution":"// ==== Square.java ====\npublic class Square {\n    private String column;\n    private int row;\n\n    public Square(String column, int row) {\n        this.column = column;\n        this.row = row;\n    }\n\n    public boolean equals(Object compared) {\n        if (this == compared) {\n            return true;\n        }\n        if (!(compared instanceof Square)) {\n            return false;\n        }\n        Square other = (Square) compared;\n        return this.column.equals(other.column) && this.row == other.row;\n    }\n\n    public int hashCode() {\n        return 31 * this.column.hashCode() + this.row;\n    }\n\n    public String toString() {\n        return this.column + this.row;\n    }\n}\n\n// ==== Main.java ====\nimport java.util.HashMap;\nimport java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        HashMap<Square, Integer> landings = new HashMap<>();\n        while (true) {\n            String line = scanner.nextLine();\n            if (line.isEmpty()) {\n                break;\n            }\n            Square square = new Square(line.substring(0, 1), Integer.valueOf(line.substring(1)));\n            landings.put(square, landings.getOrDefault(square, 0) + 1);\n        }\n        int row = Integer.valueOf(scanner.nextLine());\n        String[] columns = {\"A\", \"B\", \"C\", \"D\", \"E\", \"F\"};\n        for (String column : columns) {\n            Square square = new Square(column, row);\n            System.out.println(square + \": \" + landings.getOrDefault(square, 0));\n        }\n    }\n}\n","hints":["Square's `equals` compares the column with `equals` and the row with `==`, and `hashCode` can be `31 * this.column.hashCode() + this.row`, as in step 3. Without both, every new C4 counts as a different square, and the counts stay at 0 or 1.","Count in a `while (true)` loop that breaks at the empty line: `landings.put(square, landings.getOrDefault(square, 0) + 1);`. Then read the row with `Integer.valueOf(scanner.nextLine())`, and for each column of the array make `new Square(column, row)` and print it with `landings.getOrDefault(square, 0)`."],"tests":[{"name":"Test 1","stdin":"C4\nB4\nC4\nE2\nF4\n\n4\n","expect":"A4: 0\nB4: 1\nC4: 2\nD4: 0\nE4: 0\nF4: 1","hidden":false},{"name":"Test 2","stdin":"A1\nA1\nF6\nA1\nB1\nF1\n\n1\n","expect":"A1: 3\nB1: 1\nC1: 0\nD1: 0\nE1: 0\nF1: 1","hidden":true},{"name":"Test 3","stdin":"C3\nC3\n\n5\n","expect":"A5: 0\nB5: 0\nC5: 0\nD5: 0\nE5: 0\nF5: 0","hidden":true},{"name":"Test 4","stdin":"\n2\n","expect":"A2: 0\nB2: 0\nC2: 0\nD2: 0\nE2: 0\nF2: 0","hidden":true},{"name":"Test 5","stdin":"D6\nE6\nD6\nA6\nD6\nE6\nD5\n\n6\n","expect":"A6: 1\nB6: 0\nC6: 0\nD6: 3\nE6: 2\nF6: 0","hidden":true},{"name":"Test 6","stdin":"","call":"Square a = new Square(\"E\", 4);\nString letter = \"E\";\nSystem.out.println(a.equals(new Square(letter, 4)) + \" \" + a.equals(new Square(\"E\", 5)) + \" \" + a.equals(new Square(\"D\", 4)));\nSystem.out.println(a.hashCode() == new Square(letter, 4).hashCode());\nSystem.out.println(a.equals(\"E4\"));\n","expect":"true false false\ntrue\nfalse","hidden":true}],"require":[{"pattern":"\\bpublic\\s+boolean\\s+equals\\s*\\(\\s*Object\\s+\\w+\\s*\\)","message":"Write the header exactly as `public boolean equals(Object compared)`, so that the map uses your method."},{"pattern":"\\bpublic\\s+int\\s+hashCode\\s*\\(\\s*\\)","message":"Write the header exactly as `public int hashCode()`, so that the map uses your method."},{"pattern":"\\bnew\\s+Square\\s*\\(","message":"Make a Square with `new Square(...)` for each line you read, and one for each square of the row you print, and look it up in the map."}],"forbid":[{"pattern":"\\.keySet\\s*\\(","message":"Go through the columns of the row in order, not through the map's keys: they come in the map's own order, and a square nobody landed on isn't a key."}]}]}