{"id":"sealed-and-patterns-sealed","slug":"sealed-interfaces","title":"Sealed interfaces","text":"Any class can implement an interface: that's what makes interfaces useful for things like `Comparable`, which classes everywhere implement. But some interfaces stand for a fixed list of kinds. In the chat app, the method `preview` of the last step had to end with a `return` for \"something new\", because javac can't know that ChatMessage has only three kinds. Since Java 17, an interface can be **sealed**: it lists, after the word `permits`, exactly the classes that may implement it.\n\n### A floor plan's shapes\n\nA floor plan app draws rooms of two shapes: rectangles, and circles for round tower rooms. Nothing else is allowed:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        Shape kitchen = new Rectangle(4.0, 3.5);\n        Shape tower = new Circle(2.0);\n        System.out.println(\"Kitchen: \" + kitchen.area() + \" square meters\");\n        System.out.println(\"Tower room: \" + tower.area() + \" square meters\");\n    }\n}\n// ==== Shape.java ====\npublic sealed interface Shape permits Rectangle, Circle {\n    double area();\n}\n// ==== Rectangle.java ====\npublic final class Rectangle implements Shape {\n    private final double width;\n    private final double depth;\n\n    public Rectangle(double width, double depth) {\n        this.width = width;\n        this.depth = depth;\n    }\n\n    @Override\n    public double area() {\n        return this.width * this.depth;\n    }\n}\n// ==== Circle.java ====\npublic final class Circle implements Shape {\n    private final double radius;\n\n    public Circle(double radius) {\n        this.radius = radius;\n    }\n\n    @Override\n    public double area() {\n        return Math.PI * this.radius * this.radius;\n    }\n}\n```\n\n```output\nKitchen: 14.0 square meters\nTower room: 12.566370614359172 square meters\n```\n\n`sealed` goes before `interface`, and the list of **permitted** classes comes after `permits`, separated by commas. The interface works as any other: a Shape variable can refer to a Rectangle or a Circle, and `area()` runs the object's own method.\n\n### A class that isn't on the list\n\nA third shape that isn't on the list doesn't compile:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        Shape hall = new Triangle(3.0, 2.0);\n        System.out.println(hall.area());\n    }\n}\n// ==== Shape.java ====\npublic sealed interface Shape permits Rectangle, Circle {\n    double area();\n}\n// ==== Rectangle.java ====\npublic final class Rectangle implements Shape {\n    private final double width;\n    private final double depth;\n\n    public Rectangle(double width, double depth) {\n        this.width = width;\n        this.depth = depth;\n    }\n\n    @Override\n    public double area() {\n        return this.width * this.depth;\n    }\n}\n// ==== Circle.java ====\npublic final class Circle implements Shape {\n    private final double radius;\n\n    public Circle(double radius) {\n        this.radius = radius;\n    }\n\n    @Override\n    public double area() {\n        return Math.PI * this.radius * this.radius;\n    }\n}\n// ==== Triangle.java ====\npublic final class Triangle implements Shape {\n    private final double base;\n    private final double height;\n\n    public Triangle(double base, double height) {\n        this.base = base;\n        this.height = height;\n    }\n\n    @Override\n    public double area() {\n        return this.base * this.height / 2;\n    }\n}\n```\n\n```javac\nTriangle.java:1: error: class is not allowed to extend sealed class: Shape (as it is not listed in its 'permits' clause)\npublic final class Triangle implements Shape {\n             ^\n1 error\n```\n\njavac says \"class\" and \"extend\" for interfaces too. To allow triangles, add `Triangle` to the list: `permits Rectangle, Circle, Triangle`. The list is in one place, so a reader sees at once every kind of Shape there is.\n\n### final, sealed or non-sealed\n\nEach permitted class must say whether the list goes on below it, with one of three words:\n\n- `final`: no class can extend it. This is the usual choice, as above.\n- `sealed`: it has a `permits` list of its own.\n- `non-sealed`: any class can extend it again. Use it only when that's really what you want.\n\nWithout one of them, javac stops:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        Shape tower = new Circle(2.0);\n        System.out.println(tower.area());\n    }\n}\n// ==== Shape.java ====\npublic sealed interface Shape permits Rectangle, Circle {\n    double area();\n}\n// ==== Rectangle.java ====\npublic final class Rectangle implements Shape {\n    private final double width;\n    private final double depth;\n\n    public Rectangle(double width, double depth) {\n        this.width = width;\n        this.depth = depth;\n    }\n\n    @Override\n    public double area() {\n        return this.width * this.depth;\n    }\n}\n// ==== Circle.java ====\npublic class Circle implements Shape {\n    private final double radius;\n\n    public Circle(double radius) {\n        this.radius = radius;\n    }\n\n    @Override\n    public double area() {\n        return Math.PI * this.radius * this.radius;\n    }\n}\n```\n\n```javac\nCircle.java:1: error: sealed, non-sealed or final modifiers expected\npublic class Circle implements Shape {\n       ^\n1 error\n```\n\nThe permitted classes must also be in the same package as the sealed interface. In this module's programs, all classes are in the default package, so they are.\n\n### Records are final already\n\nA record can't be extended: every record is `final` without saying so. That makes records a natural fit for the permitted types. The floor plan's shapes as records:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        Shape hall = new Rectangle(6.0, 2.0);\n        System.out.println(hall);\n        System.out.println(hall.area());\n    }\n}\n// ==== Shape.java ====\npublic sealed interface Shape permits Rectangle, Circle {\n    double area();\n}\n// ==== Rectangle.java ====\npublic record Rectangle(double width, double depth) implements Shape {\n    @Override\n    public double area() {\n        return this.width * this.depth;\n    }\n}\n// ==== Circle.java ====\npublic record Circle(double radius) implements Shape {\n    @Override\n    public double area() {\n        return Math.PI * this.radius * this.radius;\n    }\n}\n```\n\n```output\nRectangle[width=6.0, depth=2.0]\n12.0\n```\n\nAn abstract class can be sealed in the same way (`public sealed abstract class Shape permits Rectangle, Circle`), and then its permitted classes extend it. This module uses sealed interfaces. The next step shows what the closed list is good for: a switch that javac checks covers every kind.\n","task":"A café's till takes payments. Make `Payment` a sealed interface that permits `Cash` and `BankCard`, and make `Cash` final. Then write the class `BankCard`: it's final, implements Payment, its constructor takes the card's last four digits (a String) and the amount in cents, and `receipt()` returns `card ending ` and the digits, a comma, and the cents, as below. The check runs:\n\n```java\nPayment first = new Cash(450);\nPayment second = new BankCard(\"4921\", 1250);\nSystem.out.println(first.receipt());\nSystem.out.println(second.receipt());\n```\n\nand expects:\n\n```\ncash, 450 cents\ncard ending 4921, 1250 cents\n```\n","kind":"code","seed":"// ==== Payment.java ====\npublic interface Payment {\n    String receipt();\n}\n\n// ==== Cash.java ====\npublic class Cash implements Payment {\n    private final int cents;\n\n    public Cash(int cents) {\n        this.cents = cents;\n    }\n\n    @Override\n    public String receipt() {\n        return \"cash, \" + this.cents + \" cents\";\n    }\n}\n\n// ==== BankCard.java ====\npublic class BankCard {\n\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        Payment first = new Cash(450);\n        Payment second = new BankCard(\"4921\", 1250);\n        System.out.println(first.receipt());\n        System.out.println(second.receipt());\n    }\n}\n","solution":"// ==== Payment.java ====\npublic sealed interface Payment permits Cash, BankCard {\n    String receipt();\n}\n\n// ==== Cash.java ====\npublic final class Cash implements Payment {\n    private final int cents;\n\n    public Cash(int cents) {\n        this.cents = cents;\n    }\n\n    @Override\n    public String receipt() {\n        return \"cash, \" + this.cents + \" cents\";\n    }\n}\n\n// ==== BankCard.java ====\npublic final class BankCard implements Payment {\n    private final String lastDigits;\n    private final int cents;\n\n    public BankCard(String lastDigits, int cents) {\n        this.lastDigits = lastDigits;\n        this.cents = cents;\n    }\n\n    @Override\n    public String receipt() {\n        return \"card ending \" + this.lastDigits + \", \" + this.cents + \" cents\";\n    }\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        Payment first = new Cash(450);\n        Payment second = new BankCard(\"4921\", 1250);\n        System.out.println(first.receipt());\n        System.out.println(second.receipt());\n    }\n}\n","hints":["The interface's header becomes `public sealed interface Payment permits Cash, BankCard`. Each permitted class then needs `final` before `class`.","BankCard is written like Cash: two `private final` instance variables, a constructor that stores them, and `receipt()` with `@Override`, returning `\"card ending \" + this.lastDigits + \", \" + this.cents + \" cents\"`."],"tests":[{"name":"Test 1","stdin":"","call":"Payment first = new Cash(450);\nPayment second = new BankCard(\"4921\", 1250);\nSystem.out.println(first.receipt());\nSystem.out.println(second.receipt());\n","expect":"cash, 450 cents\ncard ending 4921, 1250 cents","hidden":false},{"name":"Test 2","stdin":"","call":"Payment big = new BankCard(\"0007\", 98000);\nSystem.out.println(big.receipt());\nSystem.out.println(new BankCard(\"1200\", 5).receipt());\nSystem.out.println(new Cash(0).receipt());\n","expect":"card ending 0007, 98000 cents\ncard ending 1200, 5 cents\ncash, 0 cents","hidden":true}],"require":[{"pattern":"\\bsealed\\s+interface\\s+Payment\\s+permits\\b","message":"Make Payment a sealed interface with a `permits` list: `public sealed interface Payment permits ...`."},{"pattern":"\\bfinal\\s+class\\s+Cash\\b","message":"A permitted class must be `final`, `sealed` or `non-sealed`. Make Cash `final`."},{"pattern":"\\bfinal\\s+class\\s+BankCard\\s+implements\\s+Payment\\b","message":"Write BankCard as a class here (records come in the last challenge): `public final class BankCard implements Payment`."}],"forbid":[],"more":[{"task":"The café now sells gift vouchers too, and someone has written the class `Voucher` in `Voucher.java`. But the program doesn't compile any more. Press Run: javac says\n\n```text\nVoucher.java:1: error: class is not allowed to extend sealed class: Payment (as it is not listed in its 'permits' clause)\n```\n\nFix it while keeping Payment sealed: add Voucher to the interface's list. javac then has one more thing to say about Voucher; fix that too, by making it `final`. The program prints:\n\n```\ncash, 450 cents\ncard ending 4921, 1250 cents\nvoucher SPRING10, 1000 cents\n```\n","kind":"code","seed":"// ==== Voucher.java ====\npublic class Voucher implements Payment {\n    private final String code;\n    private final int cents;\n\n    public Voucher(String code, int cents) {\n        this.code = code;\n        this.cents = cents;\n    }\n\n    @Override\n    public String receipt() {\n        return \"voucher \" + this.code + \", \" + this.cents + \" cents\";\n    }\n}\n\n// ==== Payment.java ====\npublic sealed interface Payment permits Cash, BankCard {\n    String receipt();\n}\n\n// ==== Cash.java ====\npublic final class Cash implements Payment {\n    private final int cents;\n\n    public Cash(int cents) {\n        this.cents = cents;\n    }\n\n    @Override\n    public String receipt() {\n        return \"cash, \" + this.cents + \" cents\";\n    }\n}\n\n// ==== BankCard.java ====\npublic final class BankCard implements Payment {\n    private final String lastDigits;\n    private final int cents;\n\n    public BankCard(String lastDigits, int cents) {\n        this.lastDigits = lastDigits;\n        this.cents = cents;\n    }\n\n    @Override\n    public String receipt() {\n        return \"card ending \" + this.lastDigits + \", \" + this.cents + \" cents\";\n    }\n}\n\n// ==== Main.java ====\nimport java.util.ArrayList;\n\npublic class Main {\n    public static void main(String[] args) {\n        ArrayList<Payment> today = new ArrayList<>();\n        today.add(new Cash(450));\n        today.add(new BankCard(\"4921\", 1250));\n        today.add(new Voucher(\"SPRING10\", 1000));\n        for (Payment payment : today) {\n            System.out.println(payment.receipt());\n        }\n    }\n}\n","solution":"// ==== Voucher.java ====\npublic final class Voucher implements Payment {\n    private final String code;\n    private final int cents;\n\n    public Voucher(String code, int cents) {\n        this.code = code;\n        this.cents = cents;\n    }\n\n    @Override\n    public String receipt() {\n        return \"voucher \" + this.code + \", \" + this.cents + \" cents\";\n    }\n}\n\n// ==== Payment.java ====\npublic sealed interface Payment permits Cash, BankCard, Voucher {\n    String receipt();\n}\n\n// ==== Cash.java ====\npublic final class Cash implements Payment {\n    private final int cents;\n\n    public Cash(int cents) {\n        this.cents = cents;\n    }\n\n    @Override\n    public String receipt() {\n        return \"cash, \" + this.cents + \" cents\";\n    }\n}\n\n// ==== BankCard.java ====\npublic final class BankCard implements Payment {\n    private final String lastDigits;\n    private final int cents;\n\n    public BankCard(String lastDigits, int cents) {\n        this.lastDigits = lastDigits;\n        this.cents = cents;\n    }\n\n    @Override\n    public String receipt() {\n        return \"card ending \" + this.lastDigits + \", \" + this.cents + \" cents\";\n    }\n}\n\n// ==== Main.java ====\nimport java.util.ArrayList;\n\npublic class Main {\n    public static void main(String[] args) {\n        ArrayList<Payment> today = new ArrayList<>();\n        today.add(new Cash(450));\n        today.add(new BankCard(\"4921\", 1250));\n        today.add(new Voucher(\"SPRING10\", 1000));\n        for (Payment payment : today) {\n            System.out.println(payment.receipt());\n        }\n    }\n}\n","hints":["In `Payment.java`, the list becomes `permits Cash, BankCard, Voucher`.","javac then says `sealed, non-sealed or final modifiers expected` about Voucher: write `public final class Voucher implements Payment`."],"tests":[{"name":"Test 1","stdin":"","expect":"cash, 450 cents\ncard ending 4921, 1250 cents\nvoucher SPRING10, 1000 cents","hidden":false},{"name":"Test 2","stdin":"","call":"Payment gift = new Voucher(\"BDAY25\", 2500);\nSystem.out.println(gift.receipt());\nSystem.out.println(new Voucher(\"X\", 1).receipt());\n","expect":"voucher BDAY25, 2500 cents\nvoucher X, 1 cents","hidden":true}],"require":[{"pattern":"\\bsealed\\s+interface\\s+Payment\\s+permits\\b[^{]*\\bVoucher\\b","message":"Keep Payment sealed, and add Voucher to its `permits` list."},{"pattern":"\\bfinal\\s+class\\s+Voucher\\b","message":"Make Voucher `final`, like the other payments."}],"forbid":[]},{"task":"A web shop has two kinds of discount: a percentage off the price, and a fixed amount off. `Discount.java` holds the sealed interface; write its two permitted types in their files as records:\n\n- `PercentOff(int percent)`: `apply(price)` computes the discount `price * percent / 100` first (whole cents, so the division drops any fraction), then returns the price minus that discount;\n- `AmountOff(int cents)`: `apply(price)` returns the price minus `cents`, but never less than 0.\n\nPrices are in cents. The check runs:\n\n```java\nDiscount sale = new PercentOff(15);\nDiscount coupon = new AmountOff(500);\nSystem.out.println(sale + \": \" + sale.apply(2000));\nSystem.out.println(coupon + \": \" + coupon.apply(2000));\nSystem.out.println(coupon + \": \" + coupon.apply(300));\n```\n\nand expects:\n\n```\nPercentOff[percent=15]: 1700\nAmountOff[cents=500]: 1500\nAmountOff[cents=500]: 0\n```\n","kind":"code","seed":"// ==== PercentOff.java ====\n// write the record PercentOff here\n\n// ==== AmountOff.java ====\n// write the record AmountOff here\n\n// ==== Discount.java ====\npublic sealed interface Discount permits PercentOff, AmountOff {\n    int apply(int price);\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        Discount sale = new PercentOff(15);\n        Discount coupon = new AmountOff(500);\n        System.out.println(sale + \": \" + sale.apply(2000));\n        System.out.println(coupon + \": \" + coupon.apply(2000));\n        System.out.println(coupon + \": \" + coupon.apply(300));\n    }\n}\n","solution":"// ==== PercentOff.java ====\npublic record PercentOff(int percent) implements Discount {\n    @Override\n    public int apply(int price) {\n        return price - price * this.percent / 100;\n    }\n}\n\n// ==== AmountOff.java ====\npublic record AmountOff(int cents) implements Discount {\n    @Override\n    public int apply(int price) {\n        return Math.max(0, price - this.cents);\n    }\n}\n\n// ==== Discount.java ====\npublic sealed interface Discount permits PercentOff, AmountOff {\n    int apply(int price);\n}\n\n// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        Discount sale = new PercentOff(15);\n        Discount coupon = new AmountOff(500);\n        System.out.println(sale + \": \" + sale.apply(2000));\n        System.out.println(coupon + \": \" + coupon.apply(2000));\n        System.out.println(coupon + \": \" + coupon.apply(300));\n    }\n}\n","hints":["A record implements an interface after its components: `public record PercentOff(int percent) implements Discount { ... }`. It needs no `final`: records are final already.","In each record, write `public int apply(int price)` with `@Override`. `Math.max(0, price - this.cents)` keeps AmountOff's result from going below 0.","Compute the discount first, `price * this.percent / 100`, and subtract it from the price: the order of the integer division matters. `price * (100 - percent) / 100` can come out one cent lower."],"tests":[{"name":"Test 1","stdin":"","call":"Discount sale = new PercentOff(15);\nDiscount coupon = new AmountOff(500);\nSystem.out.println(sale + \": \" + sale.apply(2000));\nSystem.out.println(coupon + \": \" + coupon.apply(2000));\nSystem.out.println(coupon + \": \" + coupon.apply(300));\n","expect":"PercentOff[percent=15]: 1700\nAmountOff[cents=500]: 1500\nAmountOff[cents=500]: 0","hidden":false},{"name":"Test 2","stdin":"","call":"System.out.println(new PercentOff(33).apply(1999));\nSystem.out.println(new PercentOff(0).apply(750));\nSystem.out.println(new PercentOff(100).apply(750));\nSystem.out.println(new AmountOff(750).apply(750));\nSystem.out.println(new AmountOff(1).apply(750));\nSystem.out.println(new AmountOff(25).cents() + new PercentOff(10).percent());\n","expect":"1340\n750\n0\n0\n749\n35","hidden":true}],"require":[{"pattern":"\\brecord\\s+PercentOff\\s*\\(\\s*int\\s+percent\\s*\\)\\s*implements\\s+Discount\\b","message":"Declare `public record PercentOff(int percent) implements Discount`."},{"pattern":"\\brecord\\s+AmountOff\\s*\\(\\s*int\\s+cents\\s*\\)\\s*implements\\s+Discount\\b","message":"Declare `public record AmountOff(int cents) implements Discount`."}],"forbid":[]}]}