{"id":"generics-together","slug":"putting-it-together","title":"Putting it together: what the compiler checks","text":"This step builds one more generic class with tools from earlier modules, and then looks at the biggest gain from type parameters: the compiler checks the types for you, so many mistakes can't even run.\n\n### A history of the last few items\n\nA web shop shows the products you viewed most recently, and a vet's app keeps a dog's last few weights, in kilograms. Both keep the newest few items of some type and forget older ones:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        History<String> viewed = new History<>(3);\n        viewed.add(\"lamp\");\n        viewed.add(\"rug\");\n        viewed.add(\"kettle\");\n        viewed.add(\"mirror\");\n        System.out.println(viewed);\n        System.out.println(\"Latest: \" + viewed.latest());\n\n        History<Double> weights = new History<>(2);\n        weights.add(18.5);\n        weights.add(21.0);\n        weights.add(19.5);\n        System.out.println(weights);\n\n        try {\n            History<Integer> none = new History<>(0);\n            System.out.println(none);\n        } catch (IllegalArgumentException e) {\n            System.out.println(e.getMessage());\n        }\n    }\n}\n// ==== History.java ====\nimport java.util.ArrayList;\n\npublic class History<T> {\n    private ArrayList<T> items;\n    private int limit;\n\n    public History(int limit) {\n        if (limit < 1) {\n            throw new IllegalArgumentException(\"A history must keep at least 1 item, not \" + limit);\n        }\n        this.items = new ArrayList<>();\n        this.limit = limit;\n    }\n\n    public void add(T item) {\n        this.items.add(item);\n        if (this.items.size() > this.limit) {\n            this.items.remove(0);\n        }\n    }\n\n    public T latest() {\n        if (this.items.isEmpty()) {\n            throw new IllegalStateException(\"The history is empty\");\n        }\n        return this.items.get(this.items.size() - 1);\n    }\n\n    @Override\n    public String toString() {\n        return this.items.toString();\n    }\n}\n```\n\n```output\n[rug, kettle, mirror]\nLatest: mirror\n[21.0, 19.5]\nA history must keep at least 1 item, not 0\n```\n\n- Everything in History comes from earlier modules: an ArrayList, removing its first element, `toString`, and the exceptions of module 42. The only new part is T.\n- `add` puts the new item at the end, and when the list has grown past the limit, it removes the oldest, at index 0. `remove(0)` removes by index even in a `History<Integer>`, because 0 is an int.\n- The constructor refuses a limit below 1, and `latest()` on an empty history throws an IllegalStateException. A generic class checks its values like any other.\n- `toString` hands the job to the list's own `toString`, which calls `toString` on each item.\n\n### The compiler checks the types\n\nA `History<Double>`'s `add` takes only Doubles, and a `Box<Integer>` from step 1 holds only Integers. Try to put a String in one, or take a String out of one, and the program doesn't compile:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        Box<Integer> coins = new Box<>(340);\n        coins.setContent(\"a lot\");\n        String count = coins.getContent();\n        System.out.println(count);\n    }\n}\n// ==== Box.java ====\npublic class Box<T> {\n    private T content;\n\n    public Box(T content) {\n        this.content = content;\n    }\n\n    public T getContent() {\n        return this.content;\n    }\n\n    public void setContent(T content) {\n        this.content = content;\n    }\n}\n```\n\n```javac\nMain.java:4: error: incompatible types: String cannot be converted to Integer\n        coins.setContent(\"a lot\");\n                         ^\nMain.java:5: error: incompatible types: Integer cannot be converted to String\n        String count = coins.getContent();\n                                       ^\nNote: Some messages have been simplified; recompile with -Xdiags:verbose to get full output\n2 errors\n```\n\nWith step 1's ObjectBox, javac would accept the first line, and the second too once it had a cast, `(String) coins.getContent()`. Neither mistake would be caught before the program ran. The cast fails with a ClassCastException when the box still holds 340, and a String put into a box of coins goes unnoticed until some code takes it out as an Integer, maybe much later and in a different place. With a type parameter, javac finds both before the program ever runs, and points at the very lines. That's the main reason type parameters exist.\n\nThe checks go everywhere the types go: a `Rule<Integer>` can't be passed to a method that wants a `Rule<String>`, and a `Duo<String, Integer>`'s `swapped()` can't be stored in a `Duo<String, Integer>` variable.\n\n### Inside the class, T could be anything\n\nThe checks work in the other direction too. Inside Box, nobody knows yet what T will be, so this doesn't compile:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        Box<String> pegs = new Box<>(\"tent pegs\");\n        System.out.println(pegs.letters());\n    }\n}\n// ==== Box.java ====\npublic class Box<T> {\n    private T content;\n\n    public Box(T content) {\n        this.content = content;\n    }\n\n    public int letters() {\n        return this.content.length();\n    }\n}\n```\n\n```javac\nBox.java:9: error: cannot find symbol\n        return this.content.length();\n                           ^\n  symbol:   method length()\n  location: variable content of type T\n  where T is a type-variable:\n    T extends Object declared in class Box\n1 error\n```\n\nThis box happens to hold a String, but another Box could hold an Integer, which has no `length()`. The last lines of the message say what javac knows about T: `T extends Object`, some type of object or other. So inside a generic class, a value of type T can do only what every object can: the methods of Object, such as `toString()` and `equals`. That's why History, Rotation and OneOf work for any type: they store values, hand them back, print them and compare them with `equals`, and that's all. (Java can also promise more about T, as in `<T extends Comparable<T>>`, a type parameter that must be Comparable. That's called a **bounded type parameter**. You saw one in module 38's long error message; this course doesn't need them.)\n","task":"A drawing app lets you undo changes to the brush. The brush color is a String and its size an Integer, and one class handles both. In `Undoable.java`, write the class `Undoable<T>` with:\n\n- a constructor `Undoable(T start)`, which takes the first value\n- `get()`, which returns the current value\n- `set(T value)`, which changes the value and remembers the old one\n- `undo()`, which goes back to the value before the last `set`. Each `undo` goes one step further back. When only the first value is left, `undo` throws an IllegalStateException with the message `Nothing to undo`, and the value stays as it is.\n\nKeep the values in an `ArrayList<T>`, the current one last. The check runs:\n\n```java\nUndoable<String> color = new Undoable<>(\"black\");\ncolor.set(\"red\");\ncolor.set(\"blue\");\ncolor.undo();\nSystem.out.println(color.get());\ncolor.undo();\nSystem.out.println(color.get());\ntry {\n    color.undo();\n} catch (IllegalStateException e) {\n    System.out.println(e.getMessage());\n}\nSystem.out.println(color.get());\n```\n\nand expects:\n\n```\nred\nblack\nNothing to undo\nblack\n```\n","kind":"code","seed":"// ==== Undoable.java ====\nimport java.util.ArrayList;\n\npublic class Undoable {\n\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        Undoable<String> color = new Undoable<>(\"black\");\n        color.set(\"red\");\n        color.set(\"blue\");\n        color.undo();\n        System.out.println(color.get());\n        color.undo();\n        System.out.println(color.get());\n        try {\n            color.undo();\n        } catch (IllegalStateException e) {\n            System.out.println(e.getMessage());\n        }\n        System.out.println(color.get());\n    }\n}\n","solution":"// ==== Undoable.java ====\nimport java.util.ArrayList;\n\npublic class Undoable<T> {\n    private ArrayList<T> values;\n\n    public Undoable(T start) {\n        this.values = new ArrayList<>();\n        this.values.add(start);\n    }\n\n    public T get() {\n        return this.values.get(this.values.size() - 1);\n    }\n\n    public void set(T value) {\n        this.values.add(value);\n    }\n\n    public void undo() {\n        if (this.values.size() == 1) {\n            throw new IllegalStateException(\"Nothing to undo\");\n        }\n        this.values.remove(this.values.size() - 1);\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        Undoable<String> color = new Undoable<>(\"black\");\n        color.set(\"red\");\n        color.set(\"blue\");\n        color.undo();\n        System.out.println(color.get());\n        color.undo();\n        System.out.println(color.get());\n        try {\n            color.undo();\n        } catch (IllegalStateException e) {\n            System.out.println(e.getMessage());\n        }\n        System.out.println(color.get());\n    }\n}\n","hints":["The header is `public class Undoable<T>`, with one instance variable, `private ArrayList<T> values;`. The constructor makes the list and adds `start` to it, so the list is never empty.","`get` returns the last value, `this.values.get(this.values.size() - 1)`, and `set` adds to the end. `undo` first checks whether only one value is left, and throws `new IllegalStateException(\"Nothing to undo\")` if so; otherwise it removes the last value."],"tests":[{"name":"Test 1","stdin":"","call":"Undoable<String> color = new Undoable<>(\"black\");\ncolor.set(\"red\");\ncolor.set(\"blue\");\ncolor.undo();\nSystem.out.println(color.get());\ncolor.undo();\nSystem.out.println(color.get());\ntry {\n    color.undo();\n} catch (IllegalStateException e) {\n    System.out.println(e.getMessage());\n}\nSystem.out.println(color.get());\n","expect":"red\nblack\nNothing to undo\nblack","hidden":false},{"name":"Test 2","stdin":"","call":"Undoable<Integer> size = new Undoable<>(4);\ntry {\n    size.undo();\n    System.out.println(\"undone\");\n} catch (IllegalStateException e) {\n    System.out.println(\"refused: \" + e.getMessage());\n}\nsize.set(size.get() + 2);\nsize.set(size.get() * 3);\nsize.set(size.get() - 1);\nSystem.out.println(size.get());\nsize.undo();\nsize.undo();\nint now = size.get();\nSystem.out.println(now);\nsize.set(10);\nsize.undo();\nSystem.out.println(size.get());\nsize.undo();\nSystem.out.println(size.get());\n","expect":"refused: Nothing to undo\n17\n6\n6\n4","hidden":true},{"name":"Test 3","stdin":"","call":"Undoable<String> brush = new Undoable<>(\"black\");\nbrush.set(\"red\");\nbrush.set(\"black\");\nbrush.undo();\nSystem.out.println(brush.get());\nbrush.undo();\nSystem.out.println(brush.get());\n","expect":"red\nblack","hidden":true}],"require":[{"pattern":"\\bthrow\\s+new\\s+IllegalStateException\\s*\\(","message":"When there's nothing to undo, throw an IllegalStateException: `throw new IllegalStateException(\"Nothing to undo\");`."}],"forbid":[],"more":[{"task":"A game keeps a player's last few scores in this step's History. Look closely at what `remove(0)` removes. What does this program print?\n","kind":"predict","seed":"public class Main {\n    public static void main(String[] args) {\n        History<Integer> scores = new History<>(3);\n        scores.add(10);\n        scores.add(0);\n        scores.add(7);\n        scores.add(0);\n        System.out.println(scores + \" \" + scores.latest());\n        History<String> pages = new History<>(1);\n        pages.add(\"home\");\n        pages.add(\"cart\");\n        System.out.println(pages);\n    }\n}\n// ==== History.java ====\nimport java.util.ArrayList;\n\npublic class History<T> {\n    private ArrayList<T> items;\n    private int limit;\n\n    public History(int limit) {\n        if (limit < 1) {\n            throw new IllegalArgumentException(\"A history must keep at least 1 item, not \" + limit);\n        }\n        this.items = new ArrayList<>();\n        this.limit = limit;\n    }\n\n    public void add(T item) {\n        this.items.add(item);\n        if (this.items.size() > this.limit) {\n            this.items.remove(0);\n        }\n    }\n\n    public T latest() {\n        if (this.items.isEmpty()) {\n            throw new IllegalStateException(\"The history is empty\");\n        }\n        return this.items.get(this.items.size() - 1);\n    }\n\n    @Override\n    public String toString() {\n        return this.items.toString();\n    }\n}\n","solution":"public class Main {\n    public static void main(String[] args) {\n        History<Integer> scores = new History<>(3);\n        scores.add(10);\n        scores.add(0);\n        scores.add(7);\n        scores.add(0);\n        System.out.println(scores + \" \" + scores.latest());\n        History<String> pages = new History<>(1);\n        pages.add(\"home\");\n        pages.add(\"cart\");\n        System.out.println(pages);\n    }\n}\n// ==== History.java ====\nimport java.util.ArrayList;\n\npublic class History<T> {\n    private ArrayList<T> items;\n    private int limit;\n\n    public History(int limit) {\n        if (limit < 1) {\n            throw new IllegalArgumentException(\"A history must keep at least 1 item, not \" + limit);\n        }\n        this.items = new ArrayList<>();\n        this.limit = limit;\n    }\n\n    public void add(T item) {\n        this.items.add(item);\n        if (this.items.size() > this.limit) {\n            this.items.remove(0);\n        }\n    }\n\n    public T latest() {\n        if (this.items.isEmpty()) {\n            throw new IllegalStateException(\"The history is empty\");\n        }\n        return this.items.get(this.items.size() - 1);\n    }\n\n    @Override\n    public String toString() {\n        return this.items.toString();\n    }\n}\n","hints":["`remove(0)` removes the element at index 0, the oldest one, not an element whose value is 0. Inside History, `0` is an int, so it's the index version of `remove`, whatever T is.","After the fourth score, the list is `[10, 0, 7, 0]`, longer than 3, so the 10 at index 0 goes. The history of pages keeps only 1 item."],"tests":[{"name":"Output","stdin":"","expect":"[0, 7, 0] 0\n[cart]","hidden":false}],"require":[],"forbid":[],"lines":["[0, 7, 0] 0","[cart]"]},{"task":"A game's difficulty level must stay from 1 to 5, and a nickname must have at least 3 letters. Module 42's Oven refused a bad setting in its constructor and in `setDegrees`; one generic class can do that for any type, with a rule from step 4. `Rule.java`, `Between.java` and `MinLength.java` are ready. In `Guarded.java`, write the class `Guarded<T>` with:\n\n- a constructor `Guarded(T start, Rule<T> rule)`. The start value must pass the rule too: if it doesn't, the constructor throws an IllegalArgumentException with the message `Not allowed: ` followed by the value, like `Not allowed: 9`.\n- `get()`, which returns the value\n- `set(T value)`, which changes the value if the rule allows it. If not, it throws an IllegalArgumentException with the same kind of message, and the old value stays.\n\nThe check runs:\n\n```java\nGuarded<Integer> difficulty = new Guarded<>(3, new Between(1, 5));\ndifficulty.set(4);\ntry {\n    difficulty.set(9);\n} catch (IllegalArgumentException e) {\n    System.out.println(e.getMessage());\n}\nSystem.out.println(difficulty.get());\n```\n\nand expects:\n\n```\nNot allowed: 9\n4\n```\n","kind":"code","seed":"// ==== Guarded.java ====\npublic class Guarded {\n\n}\n\n// ==== Rule.java ====\npublic interface Rule<T> {\n    boolean allows(T value);\n}\n\n// ==== Between.java ====\npublic class Between implements Rule<Integer> {\n    private int min;\n    private int max;\n\n    public Between(int min, int max) {\n        this.min = min;\n        this.max = max;\n    }\n\n    @Override\n    public boolean allows(Integer value) {\n        return value >= this.min && value <= this.max;\n    }\n}\n\n// ==== MinLength.java ====\npublic class MinLength implements Rule<String> {\n    private int min;\n\n    public MinLength(int min) {\n        this.min = min;\n    }\n\n    @Override\n    public boolean allows(String value) {\n        return value.length() >= this.min;\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        Guarded<Integer> difficulty = new Guarded<>(3, new Between(1, 5));\n        difficulty.set(4);\n        try {\n            difficulty.set(9);\n        } catch (IllegalArgumentException e) {\n            System.out.println(e.getMessage());\n        }\n        System.out.println(difficulty.get());\n    }\n}\n","solution":"// ==== Guarded.java ====\npublic class Guarded<T> {\n    private T value;\n    private Rule<T> rule;\n\n    public Guarded(T start, Rule<T> rule) {\n        this.rule = rule;\n        this.set(start);\n    }\n\n    public T get() {\n        return this.value;\n    }\n\n    public void set(T value) {\n        if (!this.rule.allows(value)) {\n            throw new IllegalArgumentException(\"Not allowed: \" + value);\n        }\n        this.value = value;\n    }\n}\n\n// ==== Rule.java ====\npublic interface Rule<T> {\n    boolean allows(T value);\n}\n\n// ==== Between.java ====\npublic class Between implements Rule<Integer> {\n    private int min;\n    private int max;\n\n    public Between(int min, int max) {\n        this.min = min;\n        this.max = max;\n    }\n\n    @Override\n    public boolean allows(Integer value) {\n        return value >= this.min && value <= this.max;\n    }\n}\n\n// ==== MinLength.java ====\npublic class MinLength implements Rule<String> {\n    private int min;\n\n    public MinLength(int min) {\n        this.min = min;\n    }\n\n    @Override\n    public boolean allows(String value) {\n        return value.length() >= this.min;\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        Guarded<Integer> difficulty = new Guarded<>(3, new Between(1, 5));\n        difficulty.set(4);\n        try {\n            difficulty.set(9);\n        } catch (IllegalArgumentException e) {\n            System.out.println(e.getMessage());\n        }\n        System.out.println(difficulty.get());\n    }\n}\n","hints":["The header is `public class Guarded<T>`, with two instance variables: `private T value;` and `private Rule<T> rule;`. The rule's type argument is the same T, so a `Guarded<Integer>` takes a `Rule<Integer>`.","`set` checks first, `if (!this.rule.allows(value))`, and throws `new IllegalArgumentException(\"Not allowed: \" + value)`; only after the check does it set `this.value`. The constructor can store the rule and then call `this.set(start)`, so the start value gets the same check."],"tests":[{"name":"Test 1","stdin":"","call":"Guarded<Integer> difficulty = new Guarded<>(3, new Between(1, 5));\ndifficulty.set(4);\ntry {\n    difficulty.set(9);\n} catch (IllegalArgumentException e) {\n    System.out.println(e.getMessage());\n}\nSystem.out.println(difficulty.get());\n","expect":"Not allowed: 9\n4","hidden":false},{"name":"Test 2","stdin":"","call":"Guarded<String> nickname = new Guarded<>(\"Lumi\", new MinLength(3));\ntry {\n    nickname.set(\"Jo\");\n    System.out.println(\"accepted\");\n} catch (IllegalArgumentException e) {\n    System.out.println(\"refused: \" + e.getMessage());\n}\nnickname.set(\"Joe\");\nSystem.out.println(nickname.get().toUpperCase());\ntry {\n    new Guarded<>(0, new Between(1, 5));\n    System.out.println(\"accepted\");\n} catch (IllegalArgumentException e) {\n    System.out.println(\"refused: \" + e.getMessage());\n}\nGuarded<Integer> edge = new Guarded<>(1, new Between(1, 5));\nedge.set(5);\nint doubled = edge.get() * 2;\nSystem.out.println(doubled);\n","expect":"refused: Not allowed: Jo\nJOE\nrefused: Not allowed: 0\n10","hidden":true}],"require":[{"pattern":"\\bthrow\\s+new\\s+IllegalArgumentException\\s*\\(","message":"Refuse a value with `throw new IllegalArgumentException(...)`."},{"pattern":"\\bRule\\s*<\\s*\\w+\\s*>\\s+\\w+\\s*[;)]","message":"Keep the rule in an instance variable of type `Rule<T>`, and take it in the constructor as a `Rule<T>`."}],"forbid":[]}]}