{"id":"records-equality","slug":"equals-and-hashcode","title":"Records as keys","text":"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.\n\n### Equal when the components are equal\n\n```java\nimport java.util.HashMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        Pin first = new Pin(3, 5);\n        Pin second = new Pin(3, 5);\n        System.out.println(first == second);\n        System.out.println(first.equals(second));\n\n        HashMap<Pin, String> places = new HashMap<>();\n        places.put(new Pin(3, 5), \"bakery\");\n        places.put(new Pin(8, 1), \"library\");\n        System.out.println(places.get(new Pin(8, 1)));\n        System.out.println(places.containsKey(new Pin(1, 8)));\n    }\n}\n// ==== Pin.java ====\npublic record Pin(int x, int y) {\n}\n```\n\n```output\nfalse\ntrue\nlibrary\nfalse\n```\n\n- `first == second` is still `false`: `new` makes two objects, and `==` asks whether two references point to the very same object (module 32).\n- `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`.\n- `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.\n\n### What hashCode gives\n\nA 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.\n\n### Records in a HashSet\n\nA 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:\n\n```java\nimport java.util.HashSet;\n\npublic class Main {\n    public static void main(String[] args) {\n        HashSet<Pin> dropped = new HashSet<>();\n        System.out.println(dropped.add(new Pin(3, 5)));\n        System.out.println(dropped.add(new Pin(6, 2)));\n        System.out.println(dropped.add(new Pin(3, 5)));\n        System.out.println(dropped.size() + \" different places\");\n    }\n}\n// ==== Pin.java ====\npublic record Pin(int x, int y) {\n}\n```\n\n```output\ntrue\ntrue\nfalse\n2 different places\n```\n\n`add` returns `false` when an equal value is already in the set, and leaves the set as it was.\n","task":"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?\n","kind":"predict","seed":"import java.util.ArrayList;\n\npublic class Main {\n    public static void main(String[] args) {\n        TravelPass mine = new TravelPass(\"Mira Holm\", 4021);\n        TravelPass copy = new TravelPass(\"Mira Holm\", 4021);\n        TravelPass same = mine;\n        TravelPass other = new TravelPass(\"mira holm\", 4021);\n        System.out.println(mine == copy);\n        System.out.println(mine.equals(copy));\n        System.out.println(mine == same);\n        System.out.println(mine.equals(other));\n        ArrayList<TravelPass> lost = new ArrayList<>();\n        lost.add(copy);\n        System.out.println(lost.contains(mine));\n    }\n}\n// ==== TravelPass.java ====\npublic record TravelPass(String holder, int number) {\n}\n","solution":"import java.util.ArrayList;\n\npublic class Main {\n    public static void main(String[] args) {\n        TravelPass mine = new TravelPass(\"Mira Holm\", 4021);\n        TravelPass copy = new TravelPass(\"Mira Holm\", 4021);\n        TravelPass same = mine;\n        TravelPass other = new TravelPass(\"mira holm\", 4021);\n        System.out.println(mine == copy);\n        System.out.println(mine.equals(copy));\n        System.out.println(mine == same);\n        System.out.println(mine.equals(other));\n        ArrayList<TravelPass> lost = new ArrayList<>();\n        lost.add(copy);\n        System.out.println(lost.contains(mine));\n    }\n}\n// ==== TravelPass.java ====\npublic record TravelPass(String holder, int number) {\n}\n","hints":["`==` is true only for the very same object: `same` is `mine` itself, but `copy` was made with its own `new`. `equals` compares the components.","The names are compared with String's `equals`, which tells small letters from capitals. A list's `contains` uses `equals` too."],"tests":[{"name":"Output","stdin":"","expect":"false\ntrue\ntrue\nfalse\ntrue","hidden":false}],"require":[],"forbid":[],"lines":["false","true","true","false","true"],"more":[{"task":"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:\n\n```java\nArrayList<Cell> path = new ArrayList<>();\npath.add(new Cell(0, 0));\npath.add(new Cell(0, 1));\npath.add(new Cell(0, 0));\npath.add(new Cell(2, 1));\npath.add(new Cell(0, 0));\nHashMap<Cell, Integer> visits = countVisits(path);\nSystem.out.println(visits.size() + \" different cells\");\nSystem.out.println(visits.get(new Cell(0, 0)));\nSystem.out.println(visits.get(new Cell(2, 1)));\nSystem.out.println(visits.getOrDefault(new Cell(1, 0), 0));\n```\n\nand expects:\n\n```\n3 different cells\n3\n1\n0\n```\n","kind":"code","seed":"// ==== Main.java ====\nimport java.util.ArrayList;\nimport java.util.HashMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        ArrayList<Cell> path = new ArrayList<>();\n        path.add(new Cell(0, 0));\n        path.add(new Cell(0, 1));\n        path.add(new Cell(0, 0));\n        path.add(new Cell(2, 1));\n        path.add(new Cell(0, 0));\n        HashMap<Cell, Integer> visits = countVisits(path);\n        System.out.println(visits.size() + \" different cells\");\n        System.out.println(visits.get(new Cell(0, 0)));\n        System.out.println(visits.get(new Cell(2, 1)));\n        System.out.println(visits.getOrDefault(new Cell(1, 0), 0));\n    }\n\n    public static HashMap<Cell, Integer> countVisits(ArrayList<Cell> path) {\n        HashMap<Cell, Integer> visits = new HashMap<>();\n        return visits;\n    }\n}\n\n// ==== Cell.java ====\npublic record Cell(int x, int y) {\n}\n","solution":"// ==== Main.java ====\nimport java.util.ArrayList;\nimport java.util.HashMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        ArrayList<Cell> path = new ArrayList<>();\n        path.add(new Cell(0, 0));\n        path.add(new Cell(0, 1));\n        path.add(new Cell(0, 0));\n        path.add(new Cell(2, 1));\n        path.add(new Cell(0, 0));\n        HashMap<Cell, Integer> visits = countVisits(path);\n        System.out.println(visits.size() + \" different cells\");\n        System.out.println(visits.get(new Cell(0, 0)));\n        System.out.println(visits.get(new Cell(2, 1)));\n        System.out.println(visits.getOrDefault(new Cell(1, 0), 0));\n    }\n\n    public static HashMap<Cell, Integer> countVisits(ArrayList<Cell> path) {\n        HashMap<Cell, Integer> visits = new HashMap<>();\n        for (Cell cell : path) {\n            visits.put(cell, visits.getOrDefault(cell, 0) + 1);\n        }\n        return visits;\n    }\n}\n\n// ==== Cell.java ====\npublic record Cell(int x, int y) {\n}\n","hints":["Go through the path with a for-each loop. For each cell, put it in the map with one more than it had: `visits.getOrDefault(cell, 0) + 1`.","Cell is a record, so two `new Cell(0, 0)` objects are the same key: the map adds up their visits without any `equals` or `hashCode` of your own."],"tests":[{"name":"Test 1","stdin":"","call":"ArrayList<Cell> path = new ArrayList<>();\npath.add(new Cell(0, 0));\npath.add(new Cell(0, 1));\npath.add(new Cell(0, 0));\npath.add(new Cell(2, 1));\npath.add(new Cell(0, 0));\nHashMap<Cell, Integer> visits = countVisits(path);\nSystem.out.println(visits.size() + \" different cells\");\nSystem.out.println(visits.get(new Cell(0, 0)));\nSystem.out.println(visits.get(new Cell(2, 1)));\nSystem.out.println(visits.getOrDefault(new Cell(1, 0), 0));\n","expect":"3 different cells\n3\n1\n0","hidden":false},{"name":"Test 2","stdin":"","call":"ArrayList<Cell> path = new ArrayList<>();\nfor (int i = 0; i < 4; i++) {\n    path.add(new Cell(5, i));\n    path.add(new Cell(i, 5));\n}\nHashMap<Cell, Integer> visits = countVisits(path);\nSystem.out.println(visits.size() + \" different cells\");\nSystem.out.println(visits.get(new Cell(5, 0)) + \" \" + visits.get(new Cell(0, 5)) + \" \" + visits.get(new Cell(5, 3)));\nSystem.out.println(visits.getOrDefault(new Cell(5, 5), 0));\nSystem.out.println(countVisits(new ArrayList<>()).size());\n","expect":"8 different cells\n1 1 1\n0\n0","hidden":true},{"name":"Test 3","stdin":"","call":"ArrayList<Cell> path = new ArrayList<>();\npath.add(new Cell(1, 1));\npath.add(new Cell(1, 1));\npath.add(new Cell(1, 1));\npath.add(new Cell(1, 2));\nHashMap<Cell, Integer> visits = countVisits(path);\nSystem.out.println(visits.size() + \" \" + visits.get(new Cell(1, 1)) + \" \" + visits.get(new Cell(1, 2)));\n","expect":"2 3 1","hidden":true}],"require":[],"forbid":[]},{"task":"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:\n\n```java\nArrayList<Sighting> notes = new ArrayList<>();\nnotes.add(new Sighting(\"heron\", 4));\nnotes.add(new Sighting(\"crane\", 4));\nnotes.add(new Sighting(\"heron\", 4));\nnotes.add(new Sighting(\"heron\", 7));\nnotes.add(new Sighting(\"crane\", 4));\nArrayList<Sighting> unique = withoutRepeats(notes);\nSystem.out.println(unique);\nSystem.out.println(notes.size() + \" notes, \" + unique.size() + \" sightings\");\n```\n\nand expects:\n\n```\n[Sighting[bird=heron, square=4], Sighting[bird=crane, square=4], Sighting[bird=heron, square=7]]\n5 notes, 3 sightings\n```\n","kind":"code","seed":"// ==== Main.java ====\nimport java.util.ArrayList;\nimport java.util.HashSet;\n\npublic class Main {\n    public static void main(String[] args) {\n        ArrayList<Sighting> notes = new ArrayList<>();\n        notes.add(new Sighting(\"heron\", 4));\n        notes.add(new Sighting(\"crane\", 4));\n        notes.add(new Sighting(\"heron\", 4));\n        notes.add(new Sighting(\"heron\", 7));\n        notes.add(new Sighting(\"crane\", 4));\n        ArrayList<Sighting> unique = withoutRepeats(notes);\n        System.out.println(unique);\n        System.out.println(notes.size() + \" notes, \" + unique.size() + \" sightings\");\n    }\n\n    public static ArrayList<Sighting> withoutRepeats(ArrayList<Sighting> sightings) {\n        return sightings;\n    }\n}\n\n// ==== Sighting.java ====\npublic record Sighting(String bird, int square) {\n}\n","solution":"// ==== Main.java ====\nimport java.util.ArrayList;\nimport java.util.HashSet;\n\npublic class Main {\n    public static void main(String[] args) {\n        ArrayList<Sighting> notes = new ArrayList<>();\n        notes.add(new Sighting(\"heron\", 4));\n        notes.add(new Sighting(\"crane\", 4));\n        notes.add(new Sighting(\"heron\", 4));\n        notes.add(new Sighting(\"heron\", 7));\n        notes.add(new Sighting(\"crane\", 4));\n        ArrayList<Sighting> unique = withoutRepeats(notes);\n        System.out.println(unique);\n        System.out.println(notes.size() + \" notes, \" + unique.size() + \" sightings\");\n    }\n\n    public static ArrayList<Sighting> withoutRepeats(ArrayList<Sighting> sightings) {\n        HashSet<Sighting> seen = new HashSet<>();\n        ArrayList<Sighting> unique = new ArrayList<>();\n        for (Sighting sighting : sightings) {\n            if (seen.add(sighting)) {\n                unique.add(sighting);\n            }\n        }\n        return unique;\n    }\n}\n\n// ==== Sighting.java ====\npublic record Sighting(String bird, int square) {\n}\n","hints":["Make an empty `HashSet<Sighting>` and an empty `ArrayList<Sighting>`. Go through the sightings in order; one that isn't in the set yet goes into both.","`seen.add(sighting)` returns `true` only when the sighting wasn't in the set yet, so it can be the if's condition. The list keeps the order; the set only answers \"seen before?\"."],"tests":[{"name":"Test 1","stdin":"","call":"ArrayList<Sighting> notes = new ArrayList<>();\nnotes.add(new Sighting(\"heron\", 4));\nnotes.add(new Sighting(\"crane\", 4));\nnotes.add(new Sighting(\"heron\", 4));\nnotes.add(new Sighting(\"heron\", 7));\nnotes.add(new Sighting(\"crane\", 4));\nArrayList<Sighting> unique = withoutRepeats(notes);\nSystem.out.println(unique);\nSystem.out.println(notes.size() + \" notes, \" + unique.size() + \" sightings\");\n","expect":"[Sighting[bird=heron, square=4], Sighting[bird=crane, square=4], Sighting[bird=heron, square=7]]\n5 notes, 3 sightings","hidden":false},{"name":"Test 2","stdin":"","call":"ArrayList<Sighting> notes = new ArrayList<>();\nnotes.add(new Sighting(\"owl\", 12));\nnotes.add(new Sighting(\"owl\", 21));\nnotes.add(new Sighting(\"Owl\", 12));\nnotes.add(new Sighting(\"owl\", 12));\nnotes.add(new Sighting(\"owl\", 21));\nArrayList<Sighting> unique = withoutRepeats(notes);\nSystem.out.println(unique);\nSystem.out.println(notes.size() + \" notes, \" + unique.size() + \" sightings\");\nSystem.out.println(withoutRepeats(new ArrayList<>()));\n","expect":"[Sighting[bird=owl, square=12], Sighting[bird=owl, square=21], Sighting[bird=Owl, square=12]]\n5 notes, 3 sightings\n[]","hidden":true}],"require":[{"pattern":"HashSet\\s*<","message":"Remember the sightings you've taken in a `HashSet<Sighting>`."}],"forbid":[]}]}