{"id":"hash-maps-objects-as-values","slug":"objects-as-values","title":"Objects as values","text":"The values of a map can be objects of your own class. Then the map finds a whole object by its key. A museum's audio guide works this way: each exhibit has a number on a small sign, and visitors type the number into the guide to hear about the exhibit. The keys are the numbers, and the values are `GuideStop` objects.\n\n```java\nimport java.util.HashMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        HashMap<Integer, GuideStop> stops = new HashMap<>();\n        GuideStop harbor = new GuideStop(12, \"The Harbor at Dawn\", 3);\n        GuideStop horse = new GuideStop(27, \"Bronze Horse\", 5);\n        stops.put(harbor.getNumber(), harbor);\n        stops.put(horse.getNumber(), horse);\n\n        GuideStop stop = stops.get(27);\n        System.out.println(stop.getTitle() + \" is in room \" + stop.getRoom());\n        stop.listen();\n        stops.get(27).listen();\n        stops.get(12).listen();\n        System.out.println(stops.get(27));\n        System.out.println(stops.get(12));\n    }\n}\n// ==== GuideStop.java ====\npublic class GuideStop {\n    private int number;\n    private String title;\n    private int room;\n    private int listens;\n\n    public GuideStop(int number, String title, int room) {\n        this.number = number;\n        this.title = title;\n        this.room = room;\n        this.listens = 0;\n    }\n\n    public int getNumber() {\n        return this.number;\n    }\n\n    public String getTitle() {\n        return this.title;\n    }\n\n    public int getRoom() {\n        return this.room;\n    }\n\n    public void listen() {\n        this.listens++;\n    }\n\n    public String toString() {\n        return this.title + \" (room \" + this.room + \"), listens: \" + this.listens;\n    }\n}\n```\n\n```output\nBronze Horse is in room 5\nBronze Horse (room 5), listens: 2\nThe Harbor at Dawn (room 3), listens: 1\n```\n\n- The key comes from the object itself: `stops.put(harbor.getNumber(), harbor)`. That's common: a product found by its code, a user by their username. The key is then kept twice, in the object and in the map, and the two should match.\n- A map keeps references to its objects, as a list does (module 22). `stops.get(27)` gives a reference to the Bronze Horse object in the map, not a copy of it. So `stop.listen()` changes the object in the map, and there's no need to put it back.\n- `stops.get(27).listen()` finds the object and calls its method in one go. It's the same object again, so it now has 2 listens.\n\n### A number with no stop\n\nA visitor types 72 instead of 27. The map has no such key, `get` gives `null`, and calling a method on `null` crashes:\n\n```java\nimport java.util.HashMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        HashMap<Integer, GuideStop> stops = new HashMap<>();\n        GuideStop horse = new GuideStop(27, \"Bronze Horse\", 5);\n        stops.put(horse.getNumber(), horse);\n        stops.get(72).listen();\n    }\n}\n// ==== GuideStop.java ====\npublic class GuideStop {\n    private int number;\n    private String title;\n    private int room;\n    private int listens;\n\n    public GuideStop(int number, String title, int room) {\n        this.number = number;\n        this.title = title;\n        this.room = room;\n        this.listens = 0;\n    }\n\n    public int getNumber() {\n        return this.number;\n    }\n\n    public void listen() {\n        this.listens++;\n    }\n}\n```\n\n```crash\nException in thread \"main\" java.lang.NullPointerException: Cannot invoke \"GuideStop.listen()\" because the return value of \"java.util.HashMap.get(Object)\" is null\n\tat Main.main(Main.java:8)\n```\n\nWhen a key may be missing, keep what `get` gives in a variable, and check it before calling anything on it:\n\n```java\nGuideStop stop = stops.get(number);\nif (stop == null) {\n    System.out.println(\"No stop with the number \" + number);\n} else {\n    stop.listen();\n    System.out.println(stop);\n}\n```\n","task":"`Airport.java` is ready: an airport has a code, a city and a country, with getters and a toString. In `Main.java`, write the method `byCode(ArrayList<Airport> airports)`. It makes a new `HashMap<String, Airport>`, puts every airport of the list in it with the airport's code as the key, and returns the map. The check runs:\n\n```java\nArrayList<Airport> list = new ArrayList<>();\nlist.add(new Airport(\"OSL\", \"Oslo\", \"Norway\"));\nlist.add(new Airport(\"ICN\", \"Seoul\", \"South Korea\"));\nlist.add(new Airport(\"MEX\", \"Mexico City\", \"Mexico\"));\nHashMap<String, Airport> airports = byCode(list);\nSystem.out.println(airports.get(\"ICN\"));\nSystem.out.println(airports.get(\"MEX\").getCity());\nSystem.out.println(airports.size());\n```\n\nand expects:\n\n```\nSeoul, South Korea (ICN)\nMexico City\n3\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<Airport> list = new ArrayList<>();\n        list.add(new Airport(\"OSL\", \"Oslo\", \"Norway\"));\n        list.add(new Airport(\"ICN\", \"Seoul\", \"South Korea\"));\n        list.add(new Airport(\"MEX\", \"Mexico City\", \"Mexico\"));\n        HashMap<String, Airport> airports = byCode(list);\n        System.out.println(airports.get(\"ICN\"));\n        System.out.println(airports.get(\"MEX\").getCity());\n        System.out.println(airports.size());\n    }\n\n    // Write the method byCode here.\n}\n\n// ==== Airport.java ====\npublic class Airport {\n    private String code;\n    private String city;\n    private String country;\n\n    public Airport(String code, String city, String country) {\n        this.code = code;\n        this.city = city;\n        this.country = country;\n    }\n\n    public String getCode() {\n        return this.code;\n    }\n\n    public String getCity() {\n        return this.city;\n    }\n\n    public String getCountry() {\n        return this.country;\n    }\n\n    public String toString() {\n        return this.city + \", \" + this.country + \" (\" + this.code + \")\";\n    }\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<Airport> list = new ArrayList<>();\n        list.add(new Airport(\"OSL\", \"Oslo\", \"Norway\"));\n        list.add(new Airport(\"ICN\", \"Seoul\", \"South Korea\"));\n        list.add(new Airport(\"MEX\", \"Mexico City\", \"Mexico\"));\n        HashMap<String, Airport> airports = byCode(list);\n        System.out.println(airports.get(\"ICN\"));\n        System.out.println(airports.get(\"MEX\").getCity());\n        System.out.println(airports.size());\n    }\n\n    public static HashMap<String, Airport> byCode(ArrayList<Airport> airports) {\n        HashMap<String, Airport> map = new HashMap<>();\n        for (Airport airport : airports) {\n            map.put(airport.getCode(), airport);\n        }\n        return map;\n    }\n}\n\n// ==== Airport.java ====\npublic class Airport {\n    private String code;\n    private String city;\n    private String country;\n\n    public Airport(String code, String city, String country) {\n        this.code = code;\n        this.city = city;\n        this.country = country;\n    }\n\n    public String getCode() {\n        return this.code;\n    }\n\n    public String getCity() {\n        return this.city;\n    }\n\n    public String getCountry() {\n        return this.country;\n    }\n\n    public String toString() {\n        return this.city + \", \" + this.country + \" (\" + this.code + \")\";\n    }\n}\n","hints":["The method starts `public static HashMap<String, Airport> byCode(ArrayList<Airport> airports) {`, and its first line makes the map: `HashMap<String, Airport> map = new HashMap<>();`.","Go through the list with a for-each loop, and put each airport in the map with `map.put(airport.getCode(), airport);`. After the loop, return the map."],"tests":[{"name":"Test 1","stdin":"","call":"ArrayList<Airport> list = new ArrayList<>();\nlist.add(new Airport(\"OSL\", \"Oslo\", \"Norway\"));\nlist.add(new Airport(\"ICN\", \"Seoul\", \"South Korea\"));\nlist.add(new Airport(\"MEX\", \"Mexico City\", \"Mexico\"));\nHashMap<String, Airport> airports = byCode(list);\nSystem.out.println(airports.get(\"ICN\"));\nSystem.out.println(airports.get(\"MEX\").getCity());\nSystem.out.println(airports.size());\n","expect":"Seoul, South Korea (ICN)\nMexico City\n3","hidden":false},{"name":"Test 2","stdin":"","call":"ArrayList<Airport> list = new ArrayList<>();\nlist.add(new Airport(\"CPT\", \"Cape Town\", \"South Africa\"));\nlist.add(new Airport(\"KEF\", \"Reykjavik\", \"Iceland\"));\nHashMap<String, Airport> airports = byCode(list);\nSystem.out.println(airports.get(\"KEF\").getCountry());\nSystem.out.println(airports.get(\"CPT\"));\nSystem.out.println(airports.get(\"Cape Town\"));\nSystem.out.println(airports.size() + \" \" + (airports.get(\"CPT\") == list.get(0)));\n","expect":"Iceland\nCape Town, South Africa (CPT)\nnull\n2 true","hidden":true},{"name":"Test 3","stdin":"","call":"HashMap<String, Airport> none = byCode(new ArrayList<>());\nSystem.out.println(none.size() + \" \" + none.get(\"OSL\"));\nArrayList<Airport> list = new ArrayList<>();\nlist.add(new Airport(\"SYD\", \"Sydney\", \"Australia\"));\nHashMap<String, Airport> one = byCode(list);\nlist.add(new Airport(\"AKL\", \"Auckland\", \"New Zealand\"));\nSystem.out.println(one.size() + \" \" + one.get(\"SYD\").getCity());\n","expect":"0 null\n1 Sydney","hidden":true}],"require":[{"pattern":"\\bpublic\\s+static\\s+HashMap<String,\\s*Airport>\\s+byCode\\s*\\(\\s*ArrayList<Airport>\\s+\\w+\\s*\\)","message":"Write the method as `public static HashMap<String, Airport> byCode(ArrayList<Airport> airports)`."},{"pattern":"\\.getCode\\s*\\(","message":"Use each airport's own code, `getCode()`, as its key."}],"forbid":[],"more":[{"task":"A radio station finds the tracks it plays by a key, and one track got two keys: its title, and a short name. 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<String, Track> tracks = new HashMap<>();\n        Track bus = new Track(\"Night Bus\");\n        tracks.put(\"night bus\", bus);\n        tracks.put(\"nb\", bus);\n        tracks.get(\"night bus\").addPlay();\n        tracks.get(\"nb\").addPlay();\n        bus.addPlay();\n        System.out.println(tracks.get(\"night bus\").getPlays());\n\n        tracks.put(\"nb\", new Track(\"Neon Bridges\"));\n        tracks.get(\"nb\").addPlay();\n        System.out.println(bus.getPlays() + \" \" + tracks.get(\"nb\").getPlays());\n        System.out.println(tracks.size() + \" \" + tracks.get(\"nb\").getTitle());\n    }\n}\n// ==== Track.java ====\npublic class Track {\n    private String title;\n    private int plays;\n\n    public Track(String title) {\n        this.title = title;\n        this.plays = 0;\n    }\n\n    public String getTitle() {\n        return this.title;\n    }\n\n    public int getPlays() {\n        return this.plays;\n    }\n\n    public void addPlay() {\n        this.plays++;\n    }\n}\n","solution":"import java.util.HashMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        HashMap<String, Track> tracks = new HashMap<>();\n        Track bus = new Track(\"Night Bus\");\n        tracks.put(\"night bus\", bus);\n        tracks.put(\"nb\", bus);\n        tracks.get(\"night bus\").addPlay();\n        tracks.get(\"nb\").addPlay();\n        bus.addPlay();\n        System.out.println(tracks.get(\"night bus\").getPlays());\n\n        tracks.put(\"nb\", new Track(\"Neon Bridges\"));\n        tracks.get(\"nb\").addPlay();\n        System.out.println(bus.getPlays() + \" \" + tracks.get(\"nb\").getPlays());\n        System.out.println(tracks.size() + \" \" + tracks.get(\"nb\").getTitle());\n    }\n}\n// ==== Track.java ====\npublic class Track {\n    private String title;\n    private int plays;\n\n    public Track(String title) {\n        this.title = title;\n        this.plays = 0;\n    }\n\n    public String getTitle() {\n        return this.title;\n    }\n\n    public int getPlays() {\n        return this.plays;\n    }\n\n    public void addPlay() {\n        this.plays++;\n    }\n}\n","hints":["`new Track` runs only once for Night Bus, so both keys lead to the same object, and so does the variable `bus`. All three `addPlay` calls change that one object.","The second `put` for `nb` links the key to a new Track, Neon Bridges, which replaces the old value of `nb`. The Night Bus object doesn't change, and it's still in the map under `night bus`."],"tests":[{"name":"Output","stdin":"","expect":"3\n3 1\n2 Neon Bridges","hidden":false}],"require":[],"forbid":[],"lines":["3","3 1","2 Neon Bridges"]},{"task":"A small hotel keeps its rooms in a `HashMap<Integer, Room>`, with the room numbers as keys. `Room.java` is ready: a room is free until a guest checks in, and it has the methods `isFree()`, `checkIn(String guest)` and `getGuest()`. In `Main.java`, write the method `checkInGuest(HashMap<Integer, Room> rooms, int number, String guest)`. It returns:\n\n- `No room ` and the number, when the hotel has no room with that number,\n- `Room `, the number and ` is taken`, when someone is already in the room,\n- otherwise, it checks the guest in to the room and returns `Welcome, `, the guest, `! Room ` and the number.\n\nThe check runs:\n\n```java\nHashMap<Integer, Room> rooms = new HashMap<>();\nrooms.put(101, new Room(101));\nrooms.put(102, new Room(102));\nSystem.out.println(checkInGuest(rooms, 102, \"Achebe\"));\nSystem.out.println(checkInGuest(rooms, 102, \"Moreau\"));\nSystem.out.println(checkInGuest(rooms, 105, \"Moreau\"));\nSystem.out.println(rooms.get(102));\n```\n\nand expects:\n\n```\nWelcome, Achebe! Room 102\nRoom 102 is taken\nNo room 105\nRoom 102: Achebe\n```\n","kind":"code","seed":"// ==== Main.java ====\nimport java.util.HashMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        HashMap<Integer, Room> rooms = new HashMap<>();\n        rooms.put(101, new Room(101));\n        rooms.put(102, new Room(102));\n        System.out.println(checkInGuest(rooms, 102, \"Achebe\"));\n        System.out.println(checkInGuest(rooms, 102, \"Moreau\"));\n        System.out.println(checkInGuest(rooms, 105, \"Moreau\"));\n        System.out.println(rooms.get(102));\n    }\n\n    // Write the method checkInGuest here.\n}\n\n// ==== Room.java ====\npublic class Room {\n    private int number;\n    private String guest;\n\n    public Room(int number) {\n        this.number = number;\n        this.guest = null;\n    }\n\n    public int getNumber() {\n        return this.number;\n    }\n\n    public String getGuest() {\n        return this.guest;\n    }\n\n    public boolean isFree() {\n        return this.guest == null;\n    }\n\n    public void checkIn(String guest) {\n        this.guest = guest;\n    }\n\n    public void checkOut() {\n        this.guest = null;\n    }\n\n    public String toString() {\n        if (this.isFree()) {\n            return \"Room \" + this.number + \": free\";\n        }\n        return \"Room \" + this.number + \": \" + this.guest;\n    }\n}\n","solution":"// ==== Main.java ====\nimport java.util.HashMap;\n\npublic class Main {\n    public static void main(String[] args) {\n        HashMap<Integer, Room> rooms = new HashMap<>();\n        rooms.put(101, new Room(101));\n        rooms.put(102, new Room(102));\n        System.out.println(checkInGuest(rooms, 102, \"Achebe\"));\n        System.out.println(checkInGuest(rooms, 102, \"Moreau\"));\n        System.out.println(checkInGuest(rooms, 105, \"Moreau\"));\n        System.out.println(rooms.get(102));\n    }\n\n    public static String checkInGuest(HashMap<Integer, Room> rooms, int number, String guest) {\n        Room room = rooms.get(number);\n        if (room == null) {\n            return \"No room \" + number;\n        }\n        if (!room.isFree()) {\n            return \"Room \" + number + \" is taken\";\n        }\n        room.checkIn(guest);\n        return \"Welcome, \" + guest + \"! Room \" + number;\n    }\n}\n\n// ==== Room.java ====\npublic class Room {\n    private int number;\n    private String guest;\n\n    public Room(int number) {\n        this.number = number;\n        this.guest = null;\n    }\n\n    public int getNumber() {\n        return this.number;\n    }\n\n    public String getGuest() {\n        return this.guest;\n    }\n\n    public boolean isFree() {\n        return this.guest == null;\n    }\n\n    public void checkIn(String guest) {\n        this.guest = guest;\n    }\n\n    public void checkOut() {\n        this.guest = null;\n    }\n\n    public String toString() {\n        if (this.isFree()) {\n            return \"Room \" + this.number + \": free\";\n        }\n        return \"Room \" + this.number + \": \" + this.guest;\n    }\n}\n","hints":["Keep the room in a variable, `Room room = rooms.get(number);`. When it's `null`, the hotel has no such room: return the message before calling anything on it.","Then check `room.isFree()`. When it is free, `room.checkIn(guest);` changes the Room object that's in the map, so there's nothing to put back."],"tests":[{"name":"Test 1","stdin":"","call":"HashMap<Integer, Room> rooms = new HashMap<>();\nrooms.put(101, new Room(101));\nrooms.put(102, new Room(102));\nSystem.out.println(checkInGuest(rooms, 102, \"Achebe\"));\nSystem.out.println(checkInGuest(rooms, 102, \"Moreau\"));\nSystem.out.println(checkInGuest(rooms, 105, \"Moreau\"));\nSystem.out.println(rooms.get(102));\n","expect":"Welcome, Achebe! Room 102\nRoom 102 is taken\nNo room 105\nRoom 102: Achebe","hidden":false},{"name":"Test 2","stdin":"","call":"HashMap<Integer, Room> rooms = new HashMap<>();\nrooms.put(201, new Room(201));\nrooms.put(202, new Room(202));\nrooms.put(301, new Room(301));\nRoom first = rooms.get(201);\nSystem.out.println(checkInGuest(rooms, 201, \"Tanaka\"));\nSystem.out.println(first.getGuest() + \" \" + rooms.get(202) + \" \" + rooms.size());\nSystem.out.println(checkInGuest(rooms, 201, \"Okafor\"));\nfirst.checkOut();\nSystem.out.println(checkInGuest(rooms, 201, \"Okafor\"));\nSystem.out.println(checkInGuest(rooms, 302, \"Lindqvist\"));\nSystem.out.println(rooms.get(201) + \" \" + rooms.containsKey(302));\n","expect":"Welcome, Tanaka! Room 201\nTanaka Room 202: free 3\nRoom 201 is taken\nWelcome, Okafor! Room 201\nNo room 302\nRoom 201: Okafor false","hidden":true},{"name":"Test 3","stdin":"","call":"HashMap<Integer, Room> rooms = new HashMap<>();\nSystem.out.println(checkInGuest(rooms, 1, \"Kim\"));\nrooms.put(7, new Room(7));\nSystem.out.println(checkInGuest(rooms, 7, \"Kim\"));\nSystem.out.println(checkInGuest(rooms, 7, \"Kim\"));\n","expect":"No room 1\nWelcome, Kim! Room 7\nRoom 7 is taken","hidden":true}],"require":[{"pattern":"\\bpublic\\s+static\\s+String\\s+checkInGuest\\s*\\(\\s*HashMap<Integer,\\s*Room>\\s+\\w+\\s*,\\s*int\\s+\\w+\\s*,\\s*String\\s+\\w+\\s*\\)","message":"Write the method as `public static String checkInGuest(HashMap<Integer, Room> rooms, int number, String guest)`."}],"forbid":[]}]}