{"id":"records-methods","slug":"methods-and-factories","title":"Methods and factories","text":"A record's body can hold methods, like a class's. They can read the components, and they're how a record does more than hold its values.\n\n### Methods that use the components\n\nA shop keeps prices in whole cents, so that adding them up never meets the rounding errors of `double`. A record for an amount of money:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        Money coffee = new Money(350);\n        Money bun = Money.euros(2, 5);\n        Money order = coffee.plus(bun.times(2));\n        System.out.println(coffee + \" + 2 x \" + bun + \" = \" + order);\n        System.out.println(order.cents());\n    }\n}\n// ==== Money.java ====\npublic record Money(int cents) {\n    public static Money euros(int euros, int cents) {\n        return new Money(euros * 100 + cents);\n    }\n\n    public Money plus(Money other) {\n        return new Money(this.cents + other.cents);\n    }\n\n    public Money times(int count) {\n        return new Money(this.cents * count);\n    }\n\n    @Override\n    public String toString() {\n        int rest = this.cents % 100;\n        String zero = \"\";\n        if (rest < 10) {\n            zero = \"0\";\n        }\n        return this.cents / 100 + \".\" + zero + rest + \" EUR\";\n    }\n}\n```\n\n```output\n3.50 EUR + 2 x 2.05 EUR = 7.60 EUR\n760\n```\n\n- Inside the record, `this.cents` is the instance variable. `other.cents` works too, since `other` is a Money and the code is inside Money; `other.cents()` would be the same value.\n- `plus` and `times` don't change anything. They return a **new** Money with the result, as String's `toUpperCase` returns a new String. `coffee` is still 3.50 after `plus`.\n- A record may override `toString` (or `equals` and `hashCode`, though you rarely need to). Here the amount is shown as euros and cents, with a 0 before a cents value under 10.\n- `euros` is a **static factory**: a static method that makes a record in another way than the constructor, `Money.euros(2, 5)` for 2 euros and 5 cents. Static methods, and static variables too, are allowed in a record.\n\n### A record never changes\n\nA method can't assign a component, since its instance variable is `final`:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        Money price = new Money(350);\n        price.add(new Money(205));\n        System.out.println(price);\n    }\n}\n// ==== Money.java ====\npublic record Money(int cents) {\n    public void add(Money other) {\n        this.cents = this.cents + other.cents;\n    }\n}\n```\n\n```javac\nMoney.java:3: error: cannot assign a value to final variable cents\n        this.cents = this.cents + other.cents;\n            ^\n1 error\n```\n\nNor can a record have instance variables of its own besides its components. Everything a record holds is in its header:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        System.out.println(new Money(350));\n    }\n}\n// ==== Money.java ====\npublic record Money(int cents) {\n    private int timesUsed;\n}\n```\n\n```javac\nMoney.java:2: error: field declaration must be static\n    private int timesUsed;\n                ^\n  (consider replacing field with record component)\n1 error\n```\n\n### \"Changing\" one value: with-methods\n\nTo change one component, make a new record with that one value different and the others copied. Such methods are customarily named `with` and the component:\n\n```java\npublic Money withCents(int newCents) {\n    return new Money(newCents);\n}\n```\n\nFor a record with one component, that's just the constructor. With several components it saves the caller from copying all the others, as the second task below shows.\n","task":"A hiking app marks waypoints on a map, each at a point `x`, `y` (in kilometers). In `Waypoint.java`, add to the record `Waypoint(double x, double y)` the method `distanceTo(Waypoint other)`, which returns the straight-line distance between this waypoint and the other one: the square root (`Math.sqrt`) of the x difference squared plus the y difference squared. The check runs:\n\n```java\nWaypoint camp = new Waypoint(1.0, 2.0);\nWaypoint lake = new Waypoint(4.0, 6.0);\nSystem.out.println(camp.distanceTo(lake));\nSystem.out.println(lake.distanceTo(camp));\nSystem.out.println(camp.distanceTo(camp));\n```\n\nand expects:\n\n```\n5.0\n5.0\n0.0\n```\n","kind":"code","seed":"// ==== Waypoint.java ====\npublic record Waypoint(double x, double y) {\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        Waypoint camp = new Waypoint(1.0, 2.0);\n        Waypoint lake = new Waypoint(4.0, 6.0);\n        System.out.println(camp.distanceTo(lake));\n        System.out.println(lake.distanceTo(camp));\n        System.out.println(camp.distanceTo(camp));\n    }\n}\n","solution":"// ==== Waypoint.java ====\npublic record Waypoint(double x, double y) {\n    public double distanceTo(Waypoint other) {\n        double dx = this.x - other.x;\n        double dy = this.y - other.y;\n        return Math.sqrt(dx * dx + dy * dy);\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        Waypoint camp = new Waypoint(1.0, 2.0);\n        Waypoint lake = new Waypoint(4.0, 6.0);\n        System.out.println(camp.distanceTo(lake));\n        System.out.println(lake.distanceTo(camp));\n        System.out.println(camp.distanceTo(camp));\n    }\n}\n","hints":["The header is `public double distanceTo(Waypoint other)`, inside the record's braces. `this.x` and `other.x` are the two x values.","Keep the differences in variables: `double dx = this.x - other.x;` and the same for y. Then `return Math.sqrt(dx * dx + dy * dy);`. Squaring makes a negative difference positive, so the order doesn't matter."],"tests":[{"name":"Test 1","stdin":"","call":"Waypoint camp = new Waypoint(1.0, 2.0);\nWaypoint lake = new Waypoint(4.0, 6.0);\nSystem.out.println(camp.distanceTo(lake));\nSystem.out.println(lake.distanceTo(camp));\nSystem.out.println(camp.distanceTo(camp));\n","expect":"5.0\n5.0\n0.0","hidden":false},{"name":"Test 2","stdin":"","call":"Waypoint start = new Waypoint(0.0, 0.0);\nSystem.out.println(start.distanceTo(new Waypoint(1.0, 1.0)));\nSystem.out.println(new Waypoint(-3.0, 10.0).distanceTo(new Waypoint(2.0, -2.0)));\nSystem.out.println(new Waypoint(7.5, 1.0).distanceTo(new Waypoint(7.5, 3.5)));\n","expect":"1.4142135623730951\n13.0\n2.5","hidden":true}],"require":[],"forbid":[],"more":[{"task":"A game shows each player as an avatar with a name, a color and a level. Saved avatars never change: a change in the settings makes a new one. In `Avatar.java`, add to the record `Avatar(String name, String color, int level)` two methods:\n\n- `withColor(String newColor)`, which returns a new Avatar with the same name and level and the new color;\n- `withLevel(int newLevel)`, which returns a new Avatar with the same name and color and the new level.\n\nThe check runs:\n\n```java\nAvatar saved = new Avatar(\"Noor\", \"teal\", 3);\nAvatar repainted = saved.withColor(\"amber\");\nAvatar promoted = repainted.withLevel(4);\nSystem.out.println(saved);\nSystem.out.println(repainted);\nSystem.out.println(promoted);\n```\n\nand expects:\n\n```\nAvatar[name=Noor, color=teal, level=3]\nAvatar[name=Noor, color=amber, level=3]\nAvatar[name=Noor, color=amber, level=4]\n```\n","kind":"code","seed":"// ==== Avatar.java ====\npublic record Avatar(String name, String color, int level) {\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        Avatar saved = new Avatar(\"Noor\", \"teal\", 3);\n        Avatar repainted = saved.withColor(\"amber\");\n        Avatar promoted = repainted.withLevel(4);\n        System.out.println(saved);\n        System.out.println(repainted);\n        System.out.println(promoted);\n    }\n}\n","solution":"// ==== Avatar.java ====\npublic record Avatar(String name, String color, int level) {\n    public Avatar withColor(String newColor) {\n        return new Avatar(this.name, newColor, this.level);\n    }\n\n    public Avatar withLevel(int newLevel) {\n        return new Avatar(this.name, this.color, newLevel);\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        Avatar saved = new Avatar(\"Noor\", \"teal\", 3);\n        Avatar repainted = saved.withColor(\"amber\");\n        Avatar promoted = repainted.withLevel(4);\n        System.out.println(saved);\n        System.out.println(repainted);\n        System.out.println(promoted);\n    }\n}\n","hints":["Both methods return an Avatar: `public Avatar withColor(String newColor)`. Each returns `new Avatar(...)` with three values, in the order of the header.","In `withColor`, copy the name and the level from this avatar, and put `newColor` in the middle: `new Avatar(this.name, newColor, this.level)`. The original avatar stays as it was: nothing in a record can change."],"tests":[{"name":"Test 1","stdin":"","call":"Avatar saved = new Avatar(\"Noor\", \"teal\", 3);\nAvatar repainted = saved.withColor(\"amber\");\nAvatar promoted = repainted.withLevel(4);\nSystem.out.println(saved);\nSystem.out.println(repainted);\nSystem.out.println(promoted);\n","expect":"Avatar[name=Noor, color=teal, level=3]\nAvatar[name=Noor, color=amber, level=3]\nAvatar[name=Noor, color=amber, level=4]","hidden":false},{"name":"Test 2","stdin":"","call":"Avatar first = new Avatar(\"Elias\", \"plum\", 7);\nAvatar second = first.withLevel(1).withColor(\"gray\");\nSystem.out.println(first + \" \" + second);\nSystem.out.println(first.withLevel(7).equals(first) + \" \" + first.withColor(\"plum\").withLevel(8).equals(first));\nSystem.out.println(second.withColor(\"plum\").withLevel(7).equals(first));\n","expect":"Avatar[name=Elias, color=plum, level=7] Avatar[name=Elias, color=gray, level=1]\ntrue false\ntrue","hidden":true}],"require":[],"forbid":[]},{"task":"A timetable stores times of day as minutes after midnight, but shows them as hours and minutes. In `TimeOfDay.java`, add to the record `TimeOfDay(int hour, int minute)`:\n\n- a static factory `fromMinutes(int minutes)`, which makes the TimeOfDay that many minutes after midnight. It wraps around the day: 1440 minutes (24 hours) is midnight again, and -15 is 23:45;\n- a static factory `midnight()`, which returns 00:00;\n- `toMinutes()`, which returns how many minutes after midnight this time is;\n- `plusMinutes(int minutes)`, which returns the time that many minutes later (or earlier, for a negative number), wrapping around the day too;\n- `toString()`, which shows the time with two digits each, like `08:05`.\n\nThe check runs:\n\n```java\nTimeOfDay departure = TimeOfDay.fromMinutes(485);\nTimeOfDay delayed = departure.plusMinutes(70);\nSystem.out.println(departure + \" becomes \" + delayed);\nSystem.out.println(delayed.toMinutes() + \" \" + delayed.hour());\nSystem.out.println(new TimeOfDay(23, 50).plusMinutes(25));\nSystem.out.println(TimeOfDay.midnight().plusMinutes(-15));\n```\n\nand expects:\n\n```\n08:05 becomes 09:15\n555 9\n00:15\n23:45\n```\n","kind":"code","seed":"// ==== TimeOfDay.java ====\npublic record TimeOfDay(int hour, int minute) {\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        TimeOfDay departure = TimeOfDay.fromMinutes(485);\n        TimeOfDay delayed = departure.plusMinutes(70);\n        System.out.println(departure + \" becomes \" + delayed);\n        System.out.println(delayed.toMinutes() + \" \" + delayed.hour());\n        System.out.println(new TimeOfDay(23, 50).plusMinutes(25));\n        System.out.println(TimeOfDay.midnight().plusMinutes(-15));\n    }\n}\n","solution":"// ==== TimeOfDay.java ====\npublic record TimeOfDay(int hour, int minute) {\n    public static TimeOfDay fromMinutes(int minutes) {\n        int inDay = minutes % 1440;\n        if (inDay < 0) {\n            inDay = inDay + 1440;\n        }\n        return new TimeOfDay(inDay / 60, inDay % 60);\n    }\n\n    public static TimeOfDay midnight() {\n        return new TimeOfDay(0, 0);\n    }\n\n    public int toMinutes() {\n        return this.hour * 60 + this.minute;\n    }\n\n    public TimeOfDay plusMinutes(int minutes) {\n        return fromMinutes(this.toMinutes() + minutes);\n    }\n\n    @Override\n    public String toString() {\n        String text = \"\";\n        if (this.hour < 10) {\n            text = \"0\";\n        }\n        text = text + this.hour + \":\";\n        if (this.minute < 10) {\n            text = text + \"0\";\n        }\n        return text + this.minute;\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        TimeOfDay departure = TimeOfDay.fromMinutes(485);\n        TimeOfDay delayed = departure.plusMinutes(70);\n        System.out.println(departure + \" becomes \" + delayed);\n        System.out.println(delayed.toMinutes() + \" \" + delayed.hour());\n        System.out.println(new TimeOfDay(23, 50).plusMinutes(25));\n        System.out.println(TimeOfDay.midnight().plusMinutes(-15));\n    }\n}\n","hints":["In `fromMinutes`, `minutes % 1440` wraps a time past midnight back into the day. For a negative number `%` gives a negative remainder (-15 % 1440 is -15), so add 1440 when it's below 0. Then the hour is `inDay / 60` and the minute `inDay % 60`.","`toMinutes()` is `this.hour * 60 + this.minute`, and `plusMinutes` can reuse both: `return fromMinutes(this.toMinutes() + minutes);`. In `toString`, put a `\"0\"` in front of an hour or a minute under 10."],"tests":[{"name":"Test 1","stdin":"","call":"TimeOfDay departure = TimeOfDay.fromMinutes(485);\nTimeOfDay delayed = departure.plusMinutes(70);\nSystem.out.println(departure + \" becomes \" + delayed);\nSystem.out.println(delayed.toMinutes() + \" \" + delayed.hour());\nSystem.out.println(new TimeOfDay(23, 50).plusMinutes(25));\nSystem.out.println(TimeOfDay.midnight().plusMinutes(-15));\n","expect":"08:05 becomes 09:15\n555 9\n00:15\n23:45","hidden":false},{"name":"Test 2","stdin":"","call":"System.out.println(TimeOfDay.midnight() + \" \" + TimeOfDay.fromMinutes(1440) + \" \" + TimeOfDay.fromMinutes(-1));\nTimeOfDay late = new TimeOfDay(22, 30);\nSystem.out.println(late.plusMinutes(600) + \" \" + late.plusMinutes(-1350) + \" \" + late);\nSystem.out.println(TimeOfDay.fromMinutes(725).equals(new TimeOfDay(12, 5)) + \" \" + new TimeOfDay(13, 0).toMinutes());\nSystem.out.println(TimeOfDay.fromMinutes(3000).minute() + \" \" + TimeOfDay.fromMinutes(3000).hour());\n","expect":"00:00 00:00 23:59\n08:30 00:00 22:30\ntrue 780\n0 2","hidden":true}],"require":[{"pattern":"static\\s+TimeOfDay\\s+fromMinutes","message":"`fromMinutes` should be a static method of the record: `public static TimeOfDay fromMinutes(int minutes)`."}],"forbid":[]}]}