{"id":"sealed-and-patterns-instanceof","slug":"instanceof-patterns","title":"instanceof with a pattern","text":"This module is an extra: the MOOC doesn't cover it. It's about two features that work together. **Patterns** test what kind of object you have and give you its parts in the same step, and **sealed types** fix the list of classes that may implement an interface. With both, a method can handle each kind of object in its own way, and javac checks that no kind was forgotten. They came to Java in stages, from Java 16 to Java 21, and newer Java code uses them often, so they're worth being able to read and write. The module uses records (module 56) and switch expressions (module 57).\n\n### instanceof, then a cast\n\nIn module 36, a program asked an object for its type with `instanceof`, and then cast it to get a variable of that type. A chat app has text messages and photo messages, which are both ChatMessages. Printing the photos the old way:\n\n```java\nfor (ChatMessage message : chat) {\n    if (message instanceof PhotoMessage) {\n        PhotoMessage photo = (PhotoMessage) message;\n        System.out.println(photo.sender() + \" sent \" + photo.file());\n    }\n}\n```\n\nThe type `PhotoMessage` is written three times in two lines, and the cast can't even fail, since the line before it has just checked the type.\n\n### A type pattern\n\nSince Java 16, `instanceof` can be followed by a type and a new variable's name. `message instanceof PhotoMessage photo` checks the type, and when it matches, it declares the variable `photo`, which refers to the same object, as a PhotoMessage. `PhotoMessage photo` is a **type pattern**, and `photo` is a **pattern variable**.\n\n```java\nimport java.util.ArrayList;\n\npublic class Main {\n    public static void main(String[] args) {\n        ArrayList<ChatMessage> chat = new ArrayList<>();\n        chat.add(new TextMessage(\"Aino\", \"Lunch at noon?\"));\n        chat.add(new PhotoMessage(\"Ben\", \"menu.jpg\", 840));\n        chat.add(new TextMessage(\"Ben\", \"Sure, see you there\"));\n\n        for (ChatMessage message : chat) {\n            if (message instanceof TextMessage typed) {\n                System.out.println(typed.sender() + \": \" + typed.text());\n            } else if (message instanceof PhotoMessage photo) {\n                System.out.println(photo.sender() + \" sent \" + photo.file() + \", \" + photo.kilobytes() + \" kB\");\n            }\n        }\n    }\n}\n// ==== ChatMessage.java ====\npublic interface ChatMessage {\n    String sender();\n}\n// ==== TextMessage.java ====\npublic record TextMessage(String sender, String text) implements ChatMessage {\n}\n// ==== PhotoMessage.java ====\npublic record PhotoMessage(String sender, String file, int kilobytes) implements ChatMessage {\n}\n```\n\n```output\nAino: Lunch at noon?\nBen sent menu.jpg, 840 kB\nBen: Sure, see you there\n```\n\n- The messages are records. Each record's accessor `sender()` is the method that the interface ChatMessage asks for.\n- When the object isn't a TextMessage, the test is false and nothing is declared: there's no cast that could throw a ClassCastException.\n- `instanceof` is false for `null`, so a pattern variable is never `null`.\n\n### Where the pattern variable exists\n\nA pattern variable exists only where Java knows that the test was true. In the if's own block it can be used; in the else block it can't:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        Object value = \"Lunch at noon?\";\n        if (value instanceof String text) {\n            System.out.println(text.length());\n        } else {\n            System.out.println(text);\n        }\n    }\n}\n```\n\n```javac\nMain.java:7: error: cannot find symbol\n            System.out.println(text);\n                               ^\n  symbol:   variable text\n  location: class Main\n1 error\n```\n\nAfter `&&`, the variable can be used in the same condition, since the right side of `&&` is only evaluated when the left side is true:\n\n```java\nObject[] values = {\"Lunch at noon?\", 42, \"ok\"};\nfor (Object value : values) {\n    if (value instanceof String text && text.length() > 5) {\n        System.out.println(\"long text: \" + text);\n    }\n}\n```\n\n```output\nlong text: Lunch at noon?\n```\n\nWith `||` it doesn't work: the right side of `||` runs exactly when the test was false, and then there's no variable.\n\nThe test can also be turned around with `!`. When the method returns as soon as the object isn't of the type, the pattern variable can be used in the rest of the method:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        System.out.println(shout(\"see you there\"));\n        System.out.println(shout(42));\n    }\n\n    public static String shout(Object value) {\n        if (!(value instanceof String text)) {\n            return \"not text\";\n        }\n        return text.toUpperCase() + \"!\";\n    }\n}\n```\n\n```output\nSEE YOU THERE!\nnot text\n```\n","task":"The chat app now has stickers too. The method `preview` in `Main.java` still uses `instanceof` and a cast for each kind of message. Rewrite it with type patterns, so that it has no casts left. It should still print the same:\n\n```\nAino: Lunch at noon?\nBen sent a photo, 840 kB\nAino sent the sticker thumbs up\n```\n","kind":"code","seed":"// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        System.out.println(preview(new TextMessage(\"Aino\", \"Lunch at noon?\")));\n        System.out.println(preview(new PhotoMessage(\"Ben\", \"menu.jpg\", 840)));\n        System.out.println(preview(new StickerMessage(\"Aino\", \"thumbs up\")));\n    }\n\n    public static String preview(ChatMessage message) {\n        if (message instanceof TextMessage) {\n            TextMessage typed = (TextMessage) message;\n            return typed.sender() + \": \" + typed.text();\n        } else if (message instanceof PhotoMessage) {\n            PhotoMessage photo = (PhotoMessage) message;\n            return photo.sender() + \" sent a photo, \" + photo.kilobytes() + \" kB\";\n        } else if (message instanceof StickerMessage) {\n            StickerMessage sticker = (StickerMessage) message;\n            return sticker.sender() + \" sent the sticker \" + sticker.name();\n        }\n        return message.sender() + \" sent something new\";\n    }\n}\n\n// ==== ChatMessage.java ====\npublic interface ChatMessage {\n    String sender();\n}\n\n// ==== TextMessage.java ====\npublic record TextMessage(String sender, String text) implements ChatMessage {\n}\n\n// ==== PhotoMessage.java ====\npublic record PhotoMessage(String sender, String file, int kilobytes) implements ChatMessage {\n}\n\n// ==== StickerMessage.java ====\npublic record StickerMessage(String sender, String name) implements ChatMessage {\n}\n","solution":"// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        System.out.println(preview(new TextMessage(\"Aino\", \"Lunch at noon?\")));\n        System.out.println(preview(new PhotoMessage(\"Ben\", \"menu.jpg\", 840)));\n        System.out.println(preview(new StickerMessage(\"Aino\", \"thumbs up\")));\n    }\n\n    public static String preview(ChatMessage message) {\n        if (message instanceof TextMessage typed) {\n            return typed.sender() + \": \" + typed.text();\n        } else if (message instanceof PhotoMessage photo) {\n            return photo.sender() + \" sent a photo, \" + photo.kilobytes() + \" kB\";\n        } else if (message instanceof StickerMessage sticker) {\n            return sticker.sender() + \" sent the sticker \" + sticker.name();\n        }\n        return message.sender() + \" sent something new\";\n    }\n}\n\n// ==== ChatMessage.java ====\npublic interface ChatMessage {\n    String sender();\n}\n\n// ==== TextMessage.java ====\npublic record TextMessage(String sender, String text) implements ChatMessage {\n}\n\n// ==== PhotoMessage.java ====\npublic record PhotoMessage(String sender, String file, int kilobytes) implements ChatMessage {\n}\n\n// ==== StickerMessage.java ====\npublic record StickerMessage(String sender, String name) implements ChatMessage {\n}\n","hints":["Move each variable's name into the test: `message instanceof TextMessage typed`. Then the line with the cast can go.","Do the same for PhotoMessage and StickerMessage. The last `return` stays: javac can't know that there are no other kinds of ChatMessage (the next step changes that)."],"tests":[{"name":"Test 1","stdin":"","call":"System.out.println(preview(new TextMessage(\"Aino\", \"Lunch at noon?\")));\nSystem.out.println(preview(new PhotoMessage(\"Ben\", \"menu.jpg\", 840)));\nSystem.out.println(preview(new StickerMessage(\"Aino\", \"thumbs up\")));\n","expect":"Aino: Lunch at noon?\nBen sent a photo, 840 kB\nAino sent the sticker thumbs up","hidden":false},{"name":"Test 2","stdin":"","call":"System.out.println(preview(new StickerMessage(\"Chen\", \"party hat\")));\nSystem.out.println(preview(new TextMessage(\"Dora\", \"Running late, sorry\")));\nSystem.out.println(preview(new PhotoMessage(\"Chen\", \"cake.png\", 2310)));\n","expect":"Chen sent the sticker party hat\nDora: Running late, sorry\nChen sent a photo, 2310 kB","hidden":true}],"require":[{"pattern":"instanceof\\s+(TextMessage|PhotoMessage|StickerMessage)\\s+[A-Za-z_]\\w*","min":3,"message":"Use a type pattern in each of the three tests, such as `message instanceof TextMessage typed`."}],"forbid":[{"pattern":"\\(\\s*(TextMessage|PhotoMessage|StickerMessage)\\s*\\)","message":"Leave out the casts: the pattern variable already has the right type."}],"more":[{"task":"The chat app keeps its contacts in a list. A `ChatUser` has a nickname and a phone number, and two ChatUsers are the same person when their phone numbers are equal, whatever their nicknames. In `ChatUser.java`, write the method `equals(Object compared)` the way module 32 did, but with a pattern in place of the cast: `true` for the very same object, `false` when `compared` isn't a ChatUser (`null` included), and otherwise a comparison of the phone numbers. The class already has a `hashCode` that agrees with it. The check runs:\n\n```java\nArrayList<ChatUser> contacts = new ArrayList<>();\ncontacts.add(new ChatUser(\"Aino\", \"040 123 4567\"));\ncontacts.add(new ChatUser(\"Ben\", \"050 765 4321\"));\nChatUser newcomer = new ChatUser(\"Aino K.\", \"040 123 4567\");\nSystem.out.println(contacts.contains(newcomer));\nSystem.out.println(contacts.indexOf(new ChatUser(\"Benny\", \"050 765 4321\")));\nSystem.out.println(newcomer.equals(\"040 123 4567\"));\nSystem.out.println(newcomer.equals(null));\n```\n\nand expects:\n\n```\ntrue\n1\nfalse\nfalse\n```\n","kind":"code","seed":"// ==== ChatUser.java ====\npublic class ChatUser {\n    private String nickname;\n    private String phone;\n\n    public ChatUser(String nickname, String phone) {\n        this.nickname = nickname;\n        this.phone = phone;\n    }\n\n    public String getNickname() {\n        return this.nickname;\n    }\n\n    @Override\n    public int hashCode() {\n        return this.phone.hashCode();\n    }\n}\n\n// ==== Main.java ====\nimport java.util.ArrayList;\n\npublic class Main {\n    public static void main(String[] args) {\n        ArrayList<ChatUser> contacts = new ArrayList<>();\n        contacts.add(new ChatUser(\"Aino\", \"040 123 4567\"));\n        contacts.add(new ChatUser(\"Ben\", \"050 765 4321\"));\n        ChatUser newcomer = new ChatUser(\"Aino K.\", \"040 123 4567\");\n        System.out.println(contacts.contains(newcomer));\n        System.out.println(contacts.indexOf(new ChatUser(\"Benny\", \"050 765 4321\")));\n        System.out.println(newcomer.equals(\"040 123 4567\"));\n        System.out.println(newcomer.equals(null));\n    }\n}\n","solution":"// ==== ChatUser.java ====\npublic class ChatUser {\n    private String nickname;\n    private String phone;\n\n    public ChatUser(String nickname, String phone) {\n        this.nickname = nickname;\n        this.phone = phone;\n    }\n\n    public String getNickname() {\n        return this.nickname;\n    }\n\n    @Override\n    public boolean equals(Object compared) {\n        if (this == compared) {\n            return true;\n        }\n        if (!(compared instanceof ChatUser other)) {\n            return false;\n        }\n        return this.phone.equals(other.phone);\n    }\n\n    @Override\n    public int hashCode() {\n        return this.phone.hashCode();\n    }\n}\n\n// ==== Main.java ====\nimport java.util.ArrayList;\n\npublic class Main {\n    public static void main(String[] args) {\n        ArrayList<ChatUser> contacts = new ArrayList<>();\n        contacts.add(new ChatUser(\"Aino\", \"040 123 4567\"));\n        contacts.add(new ChatUser(\"Ben\", \"050 765 4321\"));\n        ChatUser newcomer = new ChatUser(\"Aino K.\", \"040 123 4567\");\n        System.out.println(contacts.contains(newcomer));\n        System.out.println(contacts.indexOf(new ChatUser(\"Benny\", \"050 765 4321\")));\n        System.out.println(newcomer.equals(\"040 123 4567\"));\n        System.out.println(newcomer.equals(null));\n    }\n}\n","hints":["The header is `public boolean equals(Object compared)`, with `@Override` above it. Its parameter is an Object, so first check that it's a ChatUser.","`if (!(compared instanceof ChatUser other)) { return false; }` returns for everything that isn't a ChatUser. After it, `other` is a ChatUser: `return this.phone.equals(other.phone);`."],"tests":[{"name":"Test 1","stdin":"","call":"ArrayList<ChatUser> contacts = new ArrayList<>();\ncontacts.add(new ChatUser(\"Aino\", \"040 123 4567\"));\ncontacts.add(new ChatUser(\"Ben\", \"050 765 4321\"));\nChatUser newcomer = new ChatUser(\"Aino K.\", \"040 123 4567\");\nSystem.out.println(contacts.contains(newcomer));\nSystem.out.println(contacts.indexOf(new ChatUser(\"Benny\", \"050 765 4321\")));\nSystem.out.println(newcomer.equals(\"040 123 4567\"));\nSystem.out.println(newcomer.equals(null));\n","expect":"true\n1\nfalse\nfalse","hidden":false},{"name":"Test 2","stdin":"","call":"ChatUser chen = new ChatUser(\"Chen\", \"044 555 0101\");\nSystem.out.println(chen.equals(chen));\nSystem.out.println(chen.equals(new ChatUser(\"Chen\", \"044 555 0102\")));\nSystem.out.println(new ChatUser(\"C\", \"044 555 0101\").equals(chen));\nObject stranger = new ChatUser(\"Dora\", \"045 000 1111\");\nSystem.out.println(stranger.equals(new ChatUser(\"Dee\", \"045 000 1111\")));\n","expect":"true\nfalse\ntrue\ntrue","hidden":true}],"require":[{"pattern":"public\\s+boolean\\s+equals\\s*\\(\\s*Object\\s+\\w+\\s*\\)","message":"Write `public boolean equals(Object compared)`, so that ArrayList's `contains` and `indexOf` use it."},{"pattern":"instanceof\\s+ChatUser\\s+[A-Za-z_]\\w*","message":"Test the type with a pattern, such as `compared instanceof ChatUser other`."}],"forbid":[{"pattern":"\\(\\s*ChatUser\\s*\\)","message":"No cast is needed: the pattern variable is already a ChatUser."}]},{"task":"In a chat, `@Aino` in a text message mentions Aino. Write the method `mentions(ArrayList<ChatMessage> chat, String name)` in `Main.java`. It prints each text message whose text contains `@` and the name, as `sender: text`, in the chat's order, and returns how many it printed. Other kinds of message are skipped. Write the test as one condition: a type pattern, `&&`, and a check that uses the pattern variable. The check runs:\n\n```java\nArrayList<ChatMessage> chat = new ArrayList<>();\nchat.add(new TextMessage(\"Ben\", \"@Aino lunch at noon?\"));\nchat.add(new PhotoMessage(\"Aino\", \"menu.jpg\", 840));\nchat.add(new TextMessage(\"Chen\", \"Can I join? @Ben\"));\nchat.add(new TextMessage(\"Aino\", \"Sure @Chen @Ben\"));\nSystem.out.println(mentions(chat, \"Ben\") + \" mentions\");\n```\n\nand expects:\n\n```\nChen: Can I join? @Ben\nAino: Sure @Chen @Ben\n2 mentions\n```\n","kind":"code","seed":"// ==== Main.java ====\nimport java.util.ArrayList;\n\npublic class Main {\n    public static void main(String[] args) {\n        ArrayList<ChatMessage> chat = new ArrayList<>();\n        chat.add(new TextMessage(\"Ben\", \"@Aino lunch at noon?\"));\n        chat.add(new PhotoMessage(\"Aino\", \"menu.jpg\", 840));\n        chat.add(new TextMessage(\"Chen\", \"Can I join? @Ben\"));\n        chat.add(new TextMessage(\"Aino\", \"Sure @Chen @Ben\"));\n        System.out.println(mentions(chat, \"Ben\") + \" mentions\");\n    }\n\n    public static int mentions(ArrayList<ChatMessage> chat, String name) {\n        return 0;\n    }\n}\n\n// ==== ChatMessage.java ====\npublic interface ChatMessage {\n    String sender();\n}\n\n// ==== TextMessage.java ====\npublic record TextMessage(String sender, String text) implements ChatMessage {\n}\n\n// ==== PhotoMessage.java ====\npublic record PhotoMessage(String sender, String file, int kilobytes) implements ChatMessage {\n}\n","solution":"// ==== Main.java ====\nimport java.util.ArrayList;\n\npublic class Main {\n    public static void main(String[] args) {\n        ArrayList<ChatMessage> chat = new ArrayList<>();\n        chat.add(new TextMessage(\"Ben\", \"@Aino lunch at noon?\"));\n        chat.add(new PhotoMessage(\"Aino\", \"menu.jpg\", 840));\n        chat.add(new TextMessage(\"Chen\", \"Can I join? @Ben\"));\n        chat.add(new TextMessage(\"Aino\", \"Sure @Chen @Ben\"));\n        System.out.println(mentions(chat, \"Ben\") + \" mentions\");\n    }\n\n    public static int mentions(ArrayList<ChatMessage> chat, String name) {\n        int count = 0;\n        for (ChatMessage message : chat) {\n            if (message instanceof TextMessage typed && typed.text().contains(\"@\" + name)) {\n                System.out.println(typed.sender() + \": \" + typed.text());\n                count++;\n            }\n        }\n        return count;\n    }\n}\n\n// ==== ChatMessage.java ====\npublic interface ChatMessage {\n    String sender();\n}\n\n// ==== TextMessage.java ====\npublic record TextMessage(String sender, String text) implements ChatMessage {\n}\n\n// ==== PhotoMessage.java ====\npublic record PhotoMessage(String sender, String file, int kilobytes) implements ChatMessage {\n}\n","hints":["Go through the chat with a for-each loop and count the messages you print in a variable.","The condition is `message instanceof TextMessage typed && typed.text().contains(\"@\" + name)`: the right side runs only for text messages, so `typed` can be used there."],"tests":[{"name":"Test 1","stdin":"","call":"ArrayList<ChatMessage> chat = new ArrayList<>();\nchat.add(new TextMessage(\"Ben\", \"@Aino lunch at noon?\"));\nchat.add(new PhotoMessage(\"Aino\", \"menu.jpg\", 840));\nchat.add(new TextMessage(\"Chen\", \"Can I join? @Ben\"));\nchat.add(new TextMessage(\"Aino\", \"Sure @Chen @Ben\"));\nSystem.out.println(mentions(chat, \"Ben\") + \" mentions\");\n","expect":"Chen: Can I join? @Ben\nAino: Sure @Chen @Ben\n2 mentions","hidden":false},{"name":"Test 2","stdin":"","call":"ArrayList<ChatMessage> chat = new ArrayList<>();\nchat.add(new PhotoMessage(\"@Dora\", \"dora.jpg\", 120));\nchat.add(new TextMessage(\"Ben\", \"Hi @Dora\"));\nchat.add(new TextMessage(\"Dora\", \"Hi Ben, where is @Aino?\"));\nSystem.out.println(mentions(chat, \"Dora\") + \" mentions\");\nSystem.out.println(mentions(chat, \"Chen\") + \" mentions\");\nSystem.out.println(mentions(new ArrayList<>(), \"Ben\") + \" mentions\");\n","expect":"Ben: Hi @Dora\n1 mentions\n0 mentions\n0 mentions","hidden":true}],"require":[{"pattern":"instanceof\\s+TextMessage\\s+[A-Za-z_]\\w*\\s*&&","message":"Test with one condition: `message instanceof TextMessage typed && ...`."}],"forbid":[{"pattern":"\\(\\s*TextMessage\\s*\\)","message":"No cast is needed: use the pattern variable."}]}]}