{"id":"list-and-map-internals-get-and-search","slug":"get-and-search","title":"get, indexOf and contains","text":"A list you can only add to isn't much use. This step gives Playlist the methods that read it: `get` for the song at an index, `indexOf` to find where a song is, and `contains` to ask whether it's there at all.\n\n### get, with a check on the index\n\nThe first idea is one line:\n\n```java\npublic String get(int index) {\n    return this.songs[index];\n}\n```\n\nWith a good index, it works. But take a playlist of 3 songs, whose array has 4 places. `get(3)` returns `null` from the empty place, without a word of warning, though the list has no song at index 3. And `get(7)` crashes with an ArrayIndexOutOfBoundsException that talks about an array of length 4, which the code using the list has never heard of.\n\nSo `get` checks the index against the size itself, and refuses a bad one by throwing an exception, as module 42 did with IllegalArgumentException. The right class here is **IndexOutOfBoundsException**, the parent of ArrayIndexOutOfBoundsException in module 42's family of exceptions. ArrayList's own `get` throws one too.\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        Playlist playlist = new Playlist();\n        playlist.add(\"Morning Rain\");\n        playlist.add(\"Night Train\");\n        playlist.add(\"Paper Boats\");\n        System.out.println(playlist.get(2));\n        System.out.println(playlist.get(3));\n    }\n}\n// ==== Playlist.java ====\npublic class Playlist {\n    private String[] songs;\n    private int size;\n\n    public Playlist() {\n        this.songs = new String[4];\n        this.size = 0;\n    }\n\n    public void add(String song) {\n        if (this.size == this.songs.length) {\n            grow();\n        }\n        this.songs[this.size] = song;\n        this.size++;\n    }\n\n    private void grow() {\n        String[] bigger = new String[this.songs.length * 2];\n        for (int i = 0; i < this.size; i++) {\n            bigger[i] = this.songs[i];\n        }\n        this.songs = bigger;\n    }\n\n    public String get(int index) {\n        if (index < 0 || index >= this.size) {\n            throw new IndexOutOfBoundsException(\"Index \" + index + \" out of bounds for size \" + this.size);\n        }\n        return this.songs[index];\n    }\n}\n```\n\n```output\nPaper Boats\n```\n\n```crash\nException in thread \"main\" java.lang.IndexOutOfBoundsException: Index 3 out of bounds for size 3\n\tat Playlist.get(Playlist.java:28)\n\tat Main.main(Main.java:8)\n```\n\n- The good indexes are 0 to `size - 1`. Anything below 0, or at `size` or above, is refused, even when the array happens to have a place there.\n- The message says what went wrong in the list's own terms: its size, not the length of an array nobody can see.\n- The stack trace shows both methods: the exception was thrown in `Playlist.get`, on line 28 of Playlist.java, which `main` called on line 8.\n\n### indexOf and contains\n\n`indexOf` is module 28's linear search: it looks at the songs one at a time, from index 0, and returns the index of the first song that equals the one it's given, or -1 when none does. `contains` then needs only one line:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        Playlist playlist = new Playlist();\n        playlist.add(\"Morning Rain\");\n        playlist.add(\"Night Train\");\n        playlist.add(\"Paper Boats\");\n        playlist.add(\"Night Train\");\n        playlist.add(\"Copper Sky\");\n\n        System.out.println(playlist.indexOf(\"Night Train\"));\n        System.out.println(playlist.indexOf(\"Slow River\"));\n        System.out.println(playlist.contains(\"Copper Sky\"));\n        for (int i = 0; i < playlist.size(); i++) {\n            System.out.println((i + 1) + \". \" + playlist.get(i));\n        }\n    }\n}\n// ==== Playlist.java ====\npublic class Playlist {\n    private String[] songs;\n    private int size;\n\n    public Playlist() {\n        this.songs = new String[4];\n        this.size = 0;\n    }\n\n    public void add(String song) {\n        if (this.size == this.songs.length) {\n            grow();\n        }\n        this.songs[this.size] = song;\n        this.size++;\n    }\n\n    private void grow() {\n        String[] bigger = new String[this.songs.length * 2];\n        for (int i = 0; i < this.size; i++) {\n            bigger[i] = this.songs[i];\n        }\n        this.songs = bigger;\n    }\n\n    public int size() {\n        return this.size;\n    }\n\n    public String get(int index) {\n        if (index < 0 || index >= this.size) {\n            throw new IndexOutOfBoundsException(\"Index \" + index + \" out of bounds for size \" + this.size);\n        }\n        return this.songs[index];\n    }\n\n    public int indexOf(String song) {\n        for (int i = 0; i < this.size; i++) {\n            if (this.songs[i].equals(song)) {\n                return i;\n            }\n        }\n        return -1;\n    }\n\n    public boolean contains(String song) {\n        return indexOf(song) >= 0;\n    }\n}\n```\n\n```output\n1\n-1\ntrue\n1. Morning Rain\n2. Night Train\n3. Paper Boats\n4. Night Train\n5. Copper Sky\n```\n\n- `indexOf` compares with `equals`, not `==`. A title the user types is a String object of its own, and `equals` compares the characters (modules 23 and 32). `Night Train` is in the list twice; `indexOf` returns as soon as it finds the first one.\n- The loop stops at `this.size`. After it, the array holds `null`, and `this.songs[i].equals(song)` on a `null` place would throw a NullPointerException: there's no object there to call `equals` on. (This list expects nobody to add `null` itself. Java's ArrayList allows it, and its code checks for it.)\n- `contains` asks `indexOf` and checks for -1, so the search is written only once.\n- With `size()` and `get`, a for loop goes through the list just like an ArrayList. A for-each loop over a Playlist doesn't compile: for-each works on arrays and on classes that implement Java's interface Iterable, and Playlist doesn't.\n","task":"In `GuestList.java`, the guest list from step 2 is ready. Add two methods:\n\n- `get(int index)`, which returns the name at that index. For an index below 0, or at the size or above, it throws an IndexOutOfBoundsException with the message `Index 3 out of bounds for size 3` (with the index and the size in it).\n- `contains(String name)`, which returns `true` if the name is on the list.\n\nThe check runs:\n\n```java\nGuestList guests = new GuestList();\nguests.add(\"Amara\");\nguests.add(\"Bruno\");\nguests.add(\"Chen\");\nSystem.out.println(guests.get(1));\nSystem.out.println(guests.contains(\"Chen\") + \" \" + guests.contains(\"Dana\"));\ntry {\n    System.out.println(guests.get(3));\n} catch (IndexOutOfBoundsException e) {\n    System.out.println(e.getMessage());\n}\n```\n\nand expects:\n\n```\nBruno\ntrue false\nIndex 3 out of bounds for size 3\n```\n","kind":"code","seed":"// ==== GuestList.java ====\npublic class GuestList {\n    private String[] names;\n    private int size;\n\n    public GuestList() {\n        this.names = new String[2];\n        this.size = 0;\n    }\n\n    public void add(String name) {\n        if (this.size == this.names.length) {\n            grow();\n        }\n        this.names[this.size] = name;\n        this.size++;\n    }\n\n    private void grow() {\n        String[] bigger = new String[this.names.length * 2];\n        for (int i = 0; i < this.size; i++) {\n            bigger[i] = this.names[i];\n        }\n        this.names = bigger;\n    }\n\n    public int size() {\n        return this.size;\n    }\n\n    // Write get and contains here.\n\n    @Override\n    public String toString() {\n        String text = \"\";\n        for (int i = 0; i < this.size; i++) {\n            if (i > 0) {\n                text += \", \";\n            }\n            text += this.names[i];\n        }\n        return text;\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        GuestList guests = new GuestList();\n        guests.add(\"Amara\");\n        guests.add(\"Bruno\");\n        guests.add(\"Chen\");\n        System.out.println(guests.get(1));\n        System.out.println(guests.contains(\"Chen\") + \" \" + guests.contains(\"Dana\"));\n        try {\n            System.out.println(guests.get(3));\n        } catch (IndexOutOfBoundsException e) {\n            System.out.println(e.getMessage());\n        }\n    }\n}\n","solution":"// ==== GuestList.java ====\npublic class GuestList {\n    private String[] names;\n    private int size;\n\n    public GuestList() {\n        this.names = new String[2];\n        this.size = 0;\n    }\n\n    public void add(String name) {\n        if (this.size == this.names.length) {\n            grow();\n        }\n        this.names[this.size] = name;\n        this.size++;\n    }\n\n    private void grow() {\n        String[] bigger = new String[this.names.length * 2];\n        for (int i = 0; i < this.size; i++) {\n            bigger[i] = this.names[i];\n        }\n        this.names = bigger;\n    }\n\n    public int size() {\n        return this.size;\n    }\n\n    public String get(int index) {\n        if (index < 0 || index >= this.size) {\n            throw new IndexOutOfBoundsException(\"Index \" + index + \" out of bounds for size \" + this.size);\n        }\n        return this.names[index];\n    }\n\n    public boolean contains(String name) {\n        for (int i = 0; i < this.size; i++) {\n            if (this.names[i].equals(name)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    @Override\n    public String toString() {\n        String text = \"\";\n        for (int i = 0; i < this.size; i++) {\n            if (i > 0) {\n                text += \", \";\n            }\n            text += this.names[i];\n        }\n        return text;\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        GuestList guests = new GuestList();\n        guests.add(\"Amara\");\n        guests.add(\"Bruno\");\n        guests.add(\"Chen\");\n        System.out.println(guests.get(1));\n        System.out.println(guests.contains(\"Chen\") + \" \" + guests.contains(\"Dana\"));\n        try {\n            System.out.println(guests.get(3));\n        } catch (IndexOutOfBoundsException e) {\n            System.out.println(e.getMessage());\n        }\n    }\n}\n","hints":["Check the index against the size first, as Playlist's `get` does, and refuse a bad one before reading the array. GuestList has no `indexOf`, so `contains` loops the way Playlist's `indexOf` does.","`get` starts with `if (index < 0 || index >= this.size) { throw new IndexOutOfBoundsException(\"Index \" + index + \" out of bounds for size \" + this.size); }` and then returns `this.names[index]`. `contains` loops from 0 up to `this.size`, not `this.names.length`: the places after the size hold `null`. Compare with `this.names[i].equals(name)`, and return `false` after the loop."],"tests":[{"name":"Test 1","stdin":"","call":"GuestList guests = new GuestList();\nguests.add(\"Amara\");\nguests.add(\"Bruno\");\nguests.add(\"Chen\");\nSystem.out.println(guests.get(1));\nSystem.out.println(guests.contains(\"Chen\") + \" \" + guests.contains(\"Dana\"));\ntry {\n    System.out.println(guests.get(3));\n} catch (IndexOutOfBoundsException e) {\n    System.out.println(e.getMessage());\n}\n","expect":"Bruno\ntrue false\nIndex 3 out of bounds for size 3","hidden":false},{"name":"Test 2","stdin":"","call":"GuestList guests = new GuestList();\nSystem.out.println(guests.contains(\"Amara\"));\ntry {\n    System.out.println(guests.get(0));\n} catch (IndexOutOfBoundsException e) {\n    System.out.println(e.getMessage());\n}\nguests.add(\"Eitan\");\nSystem.out.println(guests.get(0) + \" \" + guests.contains(\"Eitan\") + \" \" + guests.contains(\"eitan\"));\ntry {\n    System.out.println(guests.get(-1));\n} catch (IndexOutOfBoundsException e) {\n    System.out.println(e.getMessage());\n}\n","expect":"false\nIndex 0 out of bounds for size 0\nEitan true false\nIndex -1 out of bounds for size 1","hidden":true},{"name":"Test 3","stdin":"","call":"GuestList guests = new GuestList();\nfor (int i = 1; i <= 5; i++) {\n    guests.add(\"Guest \" + i);\n}\nString wanted = \"Guest \";\nSystem.out.println(guests.contains(wanted + 5) + \" \" + guests.contains(\"Guest 6\") + \" \" + guests.get(4));\ntry {\n    System.out.println(guests.get(5));\n} catch (IndexOutOfBoundsException e) {\n    System.out.println(e.getMessage());\n}\n","expect":"true false Guest 5\nIndex 5 out of bounds for size 5","hidden":true}],"require":[{"pattern":"\\bthrow\\s+new\\s+IndexOutOfBoundsException\\s*\\(","message":"Refuse a bad index with `throw new IndexOutOfBoundsException(...)`."}],"forbid":[],"more":[{"task":"What does this program print? `Playlist.java` has the methods from this step.\n","kind":"predict","seed":"public class Main {\n    public static void main(String[] args) {\n        Playlist list = new Playlist();\n        list.add(\"Slow River\");\n        list.add(\"Copper Sky\");\n        list.add(\"Slow River\");\n        System.out.println(list.indexOf(\"Slow River\") + \" \" + list.contains(\"copper sky\"));\n        try {\n            System.out.println(list.get(2));\n            System.out.println(list.get(3));\n        } catch (IndexOutOfBoundsException e) {\n            System.out.println(e.getMessage());\n        }\n        System.out.println(list.size());\n    }\n}\n// ==== Playlist.java ====\npublic class Playlist {\n    private String[] songs;\n    private int size;\n\n    public Playlist() {\n        this.songs = new String[4];\n        this.size = 0;\n    }\n\n    public void add(String song) {\n        if (this.size == this.songs.length) {\n            grow();\n        }\n        this.songs[this.size] = song;\n        this.size++;\n    }\n\n    private void grow() {\n        String[] bigger = new String[this.songs.length * 2];\n        for (int i = 0; i < this.size; i++) {\n            bigger[i] = this.songs[i];\n        }\n        this.songs = bigger;\n    }\n\n    public int size() {\n        return this.size;\n    }\n\n    public String get(int index) {\n        if (index < 0 || index >= this.size) {\n            throw new IndexOutOfBoundsException(\"Index \" + index + \" out of bounds for size \" + this.size);\n        }\n        return this.songs[index];\n    }\n\n    public int indexOf(String song) {\n        for (int i = 0; i < this.size; i++) {\n            if (this.songs[i].equals(song)) {\n                return i;\n            }\n        }\n        return -1;\n    }\n\n    public boolean contains(String song) {\n        return indexOf(song) >= 0;\n    }\n}\n","solution":"public class Main {\n    public static void main(String[] args) {\n        Playlist list = new Playlist();\n        list.add(\"Slow River\");\n        list.add(\"Copper Sky\");\n        list.add(\"Slow River\");\n        System.out.println(list.indexOf(\"Slow River\") + \" \" + list.contains(\"copper sky\"));\n        try {\n            System.out.println(list.get(2));\n            System.out.println(list.get(3));\n        } catch (IndexOutOfBoundsException e) {\n            System.out.println(e.getMessage());\n        }\n        System.out.println(list.size());\n    }\n}\n// ==== Playlist.java ====\npublic class Playlist {\n    private String[] songs;\n    private int size;\n\n    public Playlist() {\n        this.songs = new String[4];\n        this.size = 0;\n    }\n\n    public void add(String song) {\n        if (this.size == this.songs.length) {\n            grow();\n        }\n        this.songs[this.size] = song;\n        this.size++;\n    }\n\n    private void grow() {\n        String[] bigger = new String[this.songs.length * 2];\n        for (int i = 0; i < this.size; i++) {\n            bigger[i] = this.songs[i];\n        }\n        this.songs = bigger;\n    }\n\n    public int size() {\n        return this.size;\n    }\n\n    public String get(int index) {\n        if (index < 0 || index >= this.size) {\n            throw new IndexOutOfBoundsException(\"Index \" + index + \" out of bounds for size \" + this.size);\n        }\n        return this.songs[index];\n    }\n\n    public int indexOf(String song) {\n        for (int i = 0; i < this.size; i++) {\n            if (this.songs[i].equals(song)) {\n                return i;\n            }\n        }\n        return -1;\n    }\n\n    public boolean contains(String song) {\n        return indexOf(song) >= 0;\n    }\n}\n","hints":["`indexOf` returns the index of the first match. `equals` tells capitals from small letters, so `copper sky` isn't in the list.","The array has 4 places, but the size is 3, so `get(3)` throws. Its `println` never happens, and the `catch` block prints the message. A failed `get` changes nothing."],"tests":[{"name":"Output","stdin":"","expect":"0 false\nSlow River\nIndex 3 out of bounds for size 3\n3","hidden":false}],"require":[],"forbid":[],"lines":["0 false","Slow River","Index 3 out of bounds for size 3","3"]},{"task":"`Playlist.java` has the methods from this step. Add two more, like the ones ArrayList has:\n\n- `set(int index, String song)`, which puts the song at that index in place of the one there, and returns the song it replaced. A bad index is refused just as `get` refuses it, with the same exception and message.\n- `lastIndexOf(String song)`, which returns the index of the last song that equals the given one, or -1 if there's none.\n\nThe check runs:\n\n```java\nPlaylist list = new Playlist();\nlist.add(\"Night Train\");\nlist.add(\"Paper Boats\");\nlist.add(\"Night Train\");\nSystem.out.println(list.lastIndexOf(\"Night Train\") + \" \" + list.lastIndexOf(\"Copper Sky\"));\nString old = list.set(1, \"Slow River\");\nSystem.out.println(old + \" -> \" + list.get(1));\n```\n\nand expects:\n\n```\n2 -1\nPaper Boats -> Slow River\n```\n","kind":"code","seed":"// ==== Playlist.java ====\npublic class Playlist {\n    private String[] songs;\n    private int size;\n\n    public Playlist() {\n        this.songs = new String[4];\n        this.size = 0;\n    }\n\n    public void add(String song) {\n        if (this.size == this.songs.length) {\n            grow();\n        }\n        this.songs[this.size] = song;\n        this.size++;\n    }\n\n    private void grow() {\n        String[] bigger = new String[this.songs.length * 2];\n        for (int i = 0; i < this.size; i++) {\n            bigger[i] = this.songs[i];\n        }\n        this.songs = bigger;\n    }\n\n    public int size() {\n        return this.size;\n    }\n\n    public String get(int index) {\n        if (index < 0 || index >= this.size) {\n            throw new IndexOutOfBoundsException(\"Index \" + index + \" out of bounds for size \" + this.size);\n        }\n        return this.songs[index];\n    }\n\n    public int indexOf(String song) {\n        for (int i = 0; i < this.size; i++) {\n            if (this.songs[i].equals(song)) {\n                return i;\n            }\n        }\n        return -1;\n    }\n\n    public boolean contains(String song) {\n        return indexOf(song) >= 0;\n    }\n\n    // Write set and lastIndexOf here.\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        Playlist list = new Playlist();\n        list.add(\"Night Train\");\n        list.add(\"Paper Boats\");\n        list.add(\"Night Train\");\n        System.out.println(list.lastIndexOf(\"Night Train\") + \" \" + list.lastIndexOf(\"Copper Sky\"));\n        String old = list.set(1, \"Slow River\");\n        System.out.println(old + \" -> \" + list.get(1));\n    }\n}\n","solution":"// ==== Playlist.java ====\npublic class Playlist {\n    private String[] songs;\n    private int size;\n\n    public Playlist() {\n        this.songs = new String[4];\n        this.size = 0;\n    }\n\n    public void add(String song) {\n        if (this.size == this.songs.length) {\n            grow();\n        }\n        this.songs[this.size] = song;\n        this.size++;\n    }\n\n    private void grow() {\n        String[] bigger = new String[this.songs.length * 2];\n        for (int i = 0; i < this.size; i++) {\n            bigger[i] = this.songs[i];\n        }\n        this.songs = bigger;\n    }\n\n    public int size() {\n        return this.size;\n    }\n\n    public String get(int index) {\n        if (index < 0 || index >= this.size) {\n            throw new IndexOutOfBoundsException(\"Index \" + index + \" out of bounds for size \" + this.size);\n        }\n        return this.songs[index];\n    }\n\n    public int indexOf(String song) {\n        for (int i = 0; i < this.size; i++) {\n            if (this.songs[i].equals(song)) {\n                return i;\n            }\n        }\n        return -1;\n    }\n\n    public boolean contains(String song) {\n        return indexOf(song) >= 0;\n    }\n\n    public String set(int index, String song) {\n        String old = get(index);\n        this.songs[index] = song;\n        return old;\n    }\n\n    public int lastIndexOf(String song) {\n        for (int i = this.size - 1; i >= 0; i--) {\n            if (this.songs[i].equals(song)) {\n                return i;\n            }\n        }\n        return -1;\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        Playlist list = new Playlist();\n        list.add(\"Night Train\");\n        list.add(\"Paper Boats\");\n        list.add(\"Night Train\");\n        System.out.println(list.lastIndexOf(\"Night Train\") + \" \" + list.lastIndexOf(\"Copper Sky\"));\n        String old = list.set(1, \"Slow River\");\n        System.out.println(old + \" -> \" + list.get(1));\n    }\n}\n","hints":["`set` can let `get` do the checking: `String old = get(index);` throws for a bad index, and otherwise gives the song to return. Then put the new song at `this.songs[index]`.","`lastIndexOf` searches from the end: `for (int i = this.size - 1; i >= 0; i--)`. The first match it meets is the last one in the list."],"tests":[{"name":"Test 1","stdin":"","call":"Playlist list = new Playlist();\nlist.add(\"Night Train\");\nlist.add(\"Paper Boats\");\nlist.add(\"Night Train\");\nSystem.out.println(list.lastIndexOf(\"Night Train\") + \" \" + list.lastIndexOf(\"Copper Sky\"));\nString old = list.set(1, \"Slow River\");\nSystem.out.println(old + \" -> \" + list.get(1));\n","expect":"2 -1\nPaper Boats -> Slow River","hidden":false},{"name":"Test 2","stdin":"","call":"Playlist list = new Playlist();\nSystem.out.println(list.lastIndexOf(\"Copper Sky\"));\ntry {\n    list.set(0, \"Copper Sky\");\n    System.out.println(\"set\");\n} catch (IndexOutOfBoundsException e) {\n    System.out.println(e.getMessage());\n}\nfor (int i = 0; i < 6; i++) {\n    list.add(\"Song \" + (i % 3));\n}\nSystem.out.println(list.lastIndexOf(\"Song 0\") + \" \" + list.lastIndexOf(\"Song 2\") + \" \" + list.indexOf(\"Song 2\"));\nSystem.out.println(list.set(5, \"Encore\") + \" \" + list.get(5) + \" \" + list.size());\n","expect":"-1\nIndex 0 out of bounds for size 0\n3 5 2\nSong 2 Encore 6","hidden":true},{"name":"Test 3","stdin":"","call":"Playlist list = new Playlist();\nlist.add(\"A\");\nlist.add(\"B\");\ntry {\n    list.set(2, \"C\");\n    System.out.println(\"set\");\n} catch (IndexOutOfBoundsException e) {\n    System.out.println(e.getMessage());\n}\ntry {\n    list.set(-1, \"C\");\n    System.out.println(\"set\");\n} catch (IndexOutOfBoundsException e) {\n    System.out.println(e.getMessage());\n}\nSystem.out.println(list.get(0) + list.get(1) + \" \" + list.size());\n","expect":"Index 2 out of bounds for size 2\nIndex -1 out of bounds for size 2\nAB 2","hidden":true}],"require":[],"forbid":[]}]}