{"id":"records-compact-constructors","slug":"compact-constructors","title":"Checking values with a compact constructor","text":"The constructor Java writes for a record stores whatever it's given. Often some values make no sense: a temperature colder than absolute zero, a percentage of 140, a name with spaces around it. A record can check and tidy its values in a constructor of its own.\n\n### A compact constructor\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        Reading oulu = new Reading(\"  Oulu \", -4.5);\n        System.out.println(oulu);\n        Reading wrong = new Reading(\"Kemi\", -300.0);\n        System.out.println(wrong);\n    }\n}\n// ==== Reading.java ====\npublic record Reading(String station, double celsius) {\n    public Reading {\n        if (celsius < -273.15) {\n            throw new IllegalArgumentException(\"colder than absolute zero: \" + celsius);\n        }\n        station = station.trim();\n    }\n}\n```\n\n```output\nReading[station=Oulu, celsius=-4.5]\n```\n\n```crash\nException in thread \"main\" java.lang.IllegalArgumentException: colder than absolute zero: -300.0\n\tat Reading.<init>(Reading.java:4)\n\tat Main.main(Main.java:5)\n```\n\n`public Reading { ... }` is a **compact constructor**: the record's name with no parameter list at all. Its parameters are the components, `station` and `celsius`, and Java runs the code in the braces first and then stores the parameters in the instance variables by itself. So inside it you can:\n\n- check a value and throw an exception (module 42) when it's wrong, so that no Reading with an impossible temperature can ever exist;\n- change a parameter before it's stored: `station = station.trim();` gives the parameter a tidied value, and that's the value the record keeps. That's why `\"  Oulu \"` comes out as `Oulu`.\n\nNote that it says `station = ...`, not `this.station = ...`. The parameter is what gets stored at the end; the instance variable itself is `final`, and only Java's own assignment after your code may set it:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        System.out.println(new Reading(\" Oulu\", -4.5));\n    }\n}\n// ==== Reading.java ====\npublic record Reading(String station, double celsius) {\n    public Reading {\n        this.station = station.trim();\n    }\n}\n```\n\n```javac\nReading.java:3: error: cannot assign a value to final variable station\n        this.station = station.trim();\n            ^\n1 error\n```\n\n### The long form\n\nThe constructor that takes all the components is the record's **canonical constructor**. The compact form is a short way to write it. Here is the same one written in full, with the parameter list repeated and every `this.x = x` assignment written out:\n\n```java\npublic Reading(String station, double celsius) {\n    if (celsius < -273.15) {\n        throw new IllegalArgumentException(\"colder than absolute zero: \" + celsius);\n    }\n    this.station = station.trim();\n    this.celsius = celsius;\n}\n```\n\nBoth forms declare the same constructor, so a record can have only one of them. The compact form is what you'll usually see, since its whole point is to leave out what Java can write for you.\n\nA tidied value matters for equality too: since the records keep `Oulu` either way, `new Reading(\"  Oulu \", -4.5)` equals `new Reading(\"Oulu\", -4.5)`.\n","task":"An online chess club keeps each player's nickname and rating. Nicknames are typed in carelessly, with spaces around them and capital letters here and there, but `\" Rook99 \"` and `\"rook99\"` are the same player. In `Player.java`, give the record `Player` a compact constructor that trims the nickname and turns it into small letters (`toLowerCase()`). The check runs:\n\n```java\nPlayer typed = new Player(\"  Rook99 \", 1450);\nPlayer stored = new Player(\"rook99\", 1450);\nSystem.out.println(typed);\nSystem.out.println(typed.equals(stored));\nSystem.out.println(new Player(\"QueenSide\", 1720).nickname());\n```\n\nand expects:\n\n```\nPlayer[nickname=rook99, rating=1450]\ntrue\nqueenside\n```\n","kind":"code","seed":"// ==== Player.java ====\npublic record Player(String nickname, int rating) {\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        Player typed = new Player(\"  Rook99 \", 1450);\n        Player stored = new Player(\"rook99\", 1450);\n        System.out.println(typed);\n        System.out.println(typed.equals(stored));\n        System.out.println(new Player(\"QueenSide\", 1720).nickname());\n    }\n}\n","solution":"// ==== Player.java ====\npublic record Player(String nickname, int rating) {\n    public Player {\n        nickname = nickname.trim().toLowerCase();\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        Player typed = new Player(\"  Rook99 \", 1450);\n        Player stored = new Player(\"rook99\", 1450);\n        System.out.println(typed);\n        System.out.println(typed.equals(stored));\n        System.out.println(new Player(\"QueenSide\", 1720).nickname());\n    }\n}\n","hints":["Inside the record's braces, write `public Player { }`, with no parentheses after the name. That's the compact constructor.","Give the parameter its tidied value: `nickname = nickname.trim().toLowerCase();`. Write `nickname`, not `this.nickname`; Java stores the parameter after your code runs."],"tests":[{"name":"Test 1","stdin":"","call":"Player typed = new Player(\"  Rook99 \", 1450);\nPlayer stored = new Player(\"rook99\", 1450);\nSystem.out.println(typed);\nSystem.out.println(typed.equals(stored));\nSystem.out.println(new Player(\"QueenSide\", 1720).nickname());\n","expect":"Player[nickname=rook99, rating=1450]\ntrue\nqueenside","hidden":false},{"name":"Test 2","stdin":"","call":"Player a = new Player(\"KNIGHT_RIDER\", 1300);\nPlayer b = new Player(\"\\tknight_rider  \", 1300);\nPlayer c = new Player(\"knight_rider\", 1310);\nSystem.out.println(a + \" \" + b);\nSystem.out.println(a.equals(b) + \" \" + a.equals(c));\nSystem.out.println(new Player(\" Pawn Storm \", 980));\n","expect":"Player[nickname=knight_rider, rating=1300] Player[nickname=knight_rider, rating=1300]\ntrue false\nPlayer[nickname=pawn storm, rating=980]","hidden":true}],"require":[{"pattern":"public\\s+Player\\s*\\{","message":"Write a compact constructor, `public Player { ... }`, inside the record."}],"forbid":[],"more":[{"task":"A download manager shows how far each file has come as a percentage. A buggy server sometimes reports values below 0 or above 100. In `Download.java`, give the record `Download(String file, int percent)` a compact constructor that keeps the percentage between 0 and 100: a value below 0 becomes 0, a value above 100 becomes 100, and anything else stays as it is. The check runs:\n\n```java\nSystem.out.println(new Download(\"song.mp3\", 64));\nSystem.out.println(new Download(\"map.pdf\", -3));\nSystem.out.println(new Download(\"film.mkv\", 140));\n```\n\nand expects:\n\n```\nDownload[file=song.mp3, percent=64]\nDownload[file=map.pdf, percent=0]\nDownload[file=film.mkv, percent=100]\n```\n","kind":"code","seed":"// ==== Download.java ====\npublic record Download(String file, int percent) {\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        System.out.println(new Download(\"song.mp3\", 64));\n        System.out.println(new Download(\"map.pdf\", -3));\n        System.out.println(new Download(\"film.mkv\", 140));\n    }\n}\n","solution":"// ==== Download.java ====\npublic record Download(String file, int percent) {\n    public Download {\n        if (percent < 0) {\n            percent = 0;\n        }\n        if (percent > 100) {\n            percent = 100;\n        }\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        System.out.println(new Download(\"song.mp3\", 64));\n        System.out.println(new Download(\"map.pdf\", -3));\n        System.out.println(new Download(\"film.mkv\", 140));\n    }\n}\n","hints":["Write `public Download { ... }` inside the record. In it, `percent` is the parameter, and whatever value it has at the end is what the record keeps.","Two ifs: `if (percent < 0) { percent = 0; }` and the same for above 100. `Math.max` and `Math.min` would do it in one line too: `percent = Math.max(0, Math.min(100, percent));`."],"tests":[{"name":"Test 1","stdin":"","call":"System.out.println(new Download(\"song.mp3\", 64));\nSystem.out.println(new Download(\"map.pdf\", -3));\nSystem.out.println(new Download(\"film.mkv\", 140));\n","expect":"Download[file=song.mp3, percent=64]\nDownload[file=map.pdf, percent=0]\nDownload[file=film.mkv, percent=100]","hidden":false},{"name":"Test 2","stdin":"","call":"Download low = new Download(\"notes.txt\", -250);\nDownload full = new Download(\"notes.txt\", 100);\nSystem.out.println(low.percent() + \" \" + full.percent() + \" \" + new Download(\"logo.png\", 0).percent());\nSystem.out.println(new Download(\"game.zip\", 101).equals(full) + \" \" + new Download(\"game.zip\", 101).equals(new Download(\"game.zip\", 100)));\nSystem.out.println(new Download(\"photo.jpg\", 99));\n","expect":"0 100 0\nfalse true\nDownload[file=photo.jpg, percent=99]","hidden":true}],"require":[{"pattern":"public\\s+Download\\s*\\{","message":"Write a compact constructor, `public Download { ... }`, inside the record."}],"forbid":[]},{"task":"A print dialog takes a range of pages, from the first to the last. People sometimes type the range backwards, 9 to 4, which should print the same pages as 4 to 9. In `PageRange.java`, give the record `PageRange(int first, int last)` a compact constructor that swaps the two numbers when `first` is greater than `last`, so a PageRange is always in order. The check runs:\n\n```java\nPageRange typed = new PageRange(9, 4);\nSystem.out.println(typed);\nSystem.out.println(typed.equals(new PageRange(4, 9)));\nSystem.out.println(new PageRange(2, 2));\n```\n\nand expects:\n\n```\nPageRange[first=4, last=9]\ntrue\nPageRange[first=2, last=2]\n```\n","kind":"code","seed":"// ==== PageRange.java ====\npublic record PageRange(int first, int last) {\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        PageRange typed = new PageRange(9, 4);\n        System.out.println(typed);\n        System.out.println(typed.equals(new PageRange(4, 9)));\n        System.out.println(new PageRange(2, 2));\n    }\n}\n","solution":"// ==== PageRange.java ====\npublic record PageRange(int first, int last) {\n    public PageRange {\n        if (first > last) {\n            int smaller = last;\n            last = first;\n            first = smaller;\n        }\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        PageRange typed = new PageRange(9, 4);\n        System.out.println(typed);\n        System.out.println(typed.equals(new PageRange(4, 9)));\n        System.out.println(new PageRange(2, 2));\n    }\n}\n","hints":["Swapping two variables needs a third one to hold a value for a moment: keep `last` in a helper variable, set `last = first`, then `first` gets the helper's value.","Do it only `if (first > last)`. A range that's already in order, or one page long, stays as it is."],"tests":[{"name":"Test 1","stdin":"","call":"PageRange typed = new PageRange(9, 4);\nSystem.out.println(typed);\nSystem.out.println(typed.equals(new PageRange(4, 9)));\nSystem.out.println(new PageRange(2, 2));\n","expect":"PageRange[first=4, last=9]\ntrue\nPageRange[first=2, last=2]","hidden":false},{"name":"Test 2","stdin":"","call":"PageRange backwards = new PageRange(300, 12);\nSystem.out.println(backwards.first() + \" to \" + backwards.last());\nSystem.out.println(new PageRange(1, 50));\nSystem.out.println(new PageRange(51, 50).equals(new PageRange(50, 51)) + \" \" + new PageRange(3, 8).equals(new PageRange(8, 4)));\n","expect":"12 to 300\nPageRange[first=1, last=50]\ntrue false","hidden":true}],"require":[{"pattern":"public\\s+PageRange\\s*\\{","message":"Write a compact constructor, `public PageRange { ... }`, inside the record."}],"forbid":[]}]}