{"id":"switch-and-text-strings-and-enums","slug":"strings-and-enums","title":"switch on strings and enums","text":"### Strings\n\nSince Java 7, a switch can choose by a String. The labels are String literals, and a label matches when the String `equals` it, exactly: capitals count, and so do spaces. A radio reads commands:\n\n```java\nScanner scanner = new Scanner(System.in);\nint volume = 5;\nwhile (true) {\n    String command = scanner.nextLine();\n    if (command.equals(\"off\")) {\n        break;\n    }\n    switch (command) {\n        case \"louder\" -> volume++;\n        case \"quieter\" -> volume--;\n        case \"status\" -> System.out.println(\"Volume \" + volume);\n        default -> System.out.println(\"Unknown command: \" + command);\n    }\n}\nSystem.out.println(\"Radio off at volume \" + volume);\n```\n\n```input\nlouder\nlouder\nstatus\nQuieter\noff\n```\n\n```output\nVolume 7\nUnknown command: Quieter\nRadio off at volume 7\n```\n\n`Quieter` with a capital Q isn't `quieter`, so it goes to `default`. A program that should take both can switch on `command.toLowerCase()`.\n\nThe check for `off` stays an `if` before the switch, and here's why: inside a switch, `break` ends the switch, not the loop around it. A `break` in a case would leave the switch and go on reading commands.\n\nOne more thing to know: a switch on a String that is `null` throws a NullPointerException, as calling a method on it would.\n\n### Enums\n\nA switch on an enum (module 39) uses the constants' names as labels. A cinema's tickets come in three kinds:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        System.out.println(price(Ticket.CHILD));\n        System.out.println(price(Ticket.ADULT) + price(Ticket.SENIOR));\n    }\n\n    public static int price(Ticket ticket) {\n        return switch (ticket) {\n            case CHILD -> 6;\n            case ADULT -> 12;\n            case SENIOR -> 8;\n        };\n    }\n}\n// ==== Ticket.java ====\npublic enum Ticket {\n    CHILD, ADULT, SENIOR\n}\n```\n\n```output\n6\n20\n```\n\n- A label is the constant's name alone, `case CHILD`, not `case Ticket.CHILD`: the switch already knows the enum. (Since Java 21 the long name is allowed too, but the short one is the custom.)\n- This switch expression has no `default`, and it doesn't need one: an enum has only the values it lists, and the three cases cover them all, so javac knows the switch always gives a value.\n\n### When the enum grows\n\nLeaving out `default` pays off when someone adds a constant. Say the cinema starts selling STUDENT tickets:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        System.out.println(price(Ticket.STUDENT));\n    }\n\n    public static int price(Ticket ticket) {\n        return switch (ticket) {\n            case CHILD -> 6;\n            case ADULT -> 12;\n            case SENIOR -> 8;\n        };\n    }\n}\n// ==== Ticket.java ====\npublic enum Ticket {\n    CHILD, ADULT, SENIOR, STUDENT\n}\n```\n\n```javac\nMain.java:7: error: the switch expression does not cover all possible input values\n        return switch (ticket) {\n               ^\n1 error\n```\n\njavac points at every switch expression that has no case for STUDENT, and once each has one, the program is complete again. With `default -> 0;` in the switch, it would have compiled, and every student would have got in for free without anyone noticing. So for an enum, list every constant and leave `default` out.\n\nA switch *statement* on an enum, with arrows or with colons, may leave constants out, like a switch statement on an int.\n","task":"A drinks machine is run by commands, one per line, until the line `quit`. It keeps a credit of coins, at first 0. Write the switch on the command, so that:\n\n- `coin` adds one coin to the credit and prints the credit, like `Credit: 2`\n- `tea` costs 2 coins: if the credit is at least 2, it takes them and prints `Here is your tea`; otherwise it prints `Tea costs 2 coins`\n- `refund` prints the coins given back, like `Returned 3 coins`, and sets the credit to 0\n- any other command prints `Unknown command: ` and the command\n\nThe loop and the check for `quit` are written. With this input:\n\n```input\ncoin\ntea\ncoin\ncoin\ncoin\ntea\nrefund\ndance\nquit\n```\n\nthe program prints:\n\n```\nCredit: 1\nTea costs 2 coins\nCredit: 2\nCredit: 3\nCredit: 4\nHere is your tea\nReturned 2 coins\nUnknown command: dance\nBye\n```\n","kind":"code","seed":"import java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        int credit = 0;\n        while (true) {\n            String command = scanner.nextLine();\n            if (command.equals(\"quit\")) {\n                break;\n            }\n            // react to the command here, with a switch\n\n        }\n        System.out.println(\"Bye\");\n    }\n}\n","solution":"import java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        Scanner scanner = new Scanner(System.in);\n        int credit = 0;\n        while (true) {\n            String command = scanner.nextLine();\n            if (command.equals(\"quit\")) {\n                break;\n            }\n            switch (command) {\n                case \"coin\" -> {\n                    credit++;\n                    System.out.println(\"Credit: \" + credit);\n                }\n                case \"tea\" -> {\n                    if (credit >= 2) {\n                        credit -= 2;\n                        System.out.println(\"Here is your tea\");\n                    } else {\n                        System.out.println(\"Tea costs 2 coins\");\n                    }\n                }\n                case \"refund\" -> {\n                    System.out.println(\"Returned \" + credit + \" coins\");\n                    credit = 0;\n                }\n                default -> System.out.println(\"Unknown command: \" + command);\n            }\n        }\n        System.out.println(\"Bye\");\n    }\n}\n","hints":["Write `switch (command) {` with a case for each word: `case \"coin\" -> { ... }`. A case with two statements needs braces.","The `tea` case holds an `if`: enough credit or not. `default -> ...` catches every other word."],"tests":[{"name":"Test 1","stdin":"coin\ntea\ncoin\ncoin\ncoin\ntea\nrefund\ndance\nquit\n","expect":"Credit: 1\nTea costs 2 coins\nCredit: 2\nCredit: 3\nCredit: 4\nHere is your tea\nReturned 2 coins\nUnknown command: dance\nBye","hidden":false},{"name":"Test 2","stdin":"refund\ncoin\ncoin\ncoin\ncoin\ntea\ntea\ntea\nCoin\nrefund\nquit\n","expect":"Returned 0 coins\nCredit: 1\nCredit: 2\nCredit: 3\nCredit: 4\nHere is your tea\nHere is your tea\nTea costs 2 coins\nUnknown command: Coin\nReturned 0 coins\nBye","hidden":true},{"name":"Test 3","stdin":"quit\n","expect":"Bye","hidden":true}],"require":[{"pattern":"\\bswitch\\s*\\(\\s*command\\s*\\)","message":"Choose what to do with `switch (command)`."}],"forbid":[{"pattern":"\\.equals\\s*\\(\\s*\"(coin|tea|refund)\"","message":"Let the switch's labels compare the command: `case \"coin\" -> ...`."}],"more":[{"task":"A car wash charges by the program. `Wash.java` has the enum `Wash`, with the constants `BASIC`, `FOAM` and `DELUXE`. In `Main.java`, write the method `washPrice(Wash wash)`, which returns the price in euros with a switch expression: 4 for BASIC, 7 for FOAM and 12 for DELUXE. Cover every constant, and leave out `default`. The check runs `System.out.println(washPrice(Wash.FOAM));`, which prints:\n\n```\n7\n```\n","kind":"code","seed":"// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        System.out.println(washPrice(Wash.FOAM));\n    }\n\n    public static int washPrice(Wash wash) {\n        return 0;\n    }\n}\n\n// ==== Wash.java ====\npublic enum Wash {\n    BASIC, FOAM, DELUXE\n}\n","solution":"// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        System.out.println(washPrice(Wash.FOAM));\n    }\n\n    public static int washPrice(Wash wash) {\n        return switch (wash) {\n            case BASIC -> 4;\n            case FOAM -> 7;\n            case DELUXE -> 12;\n        };\n    }\n}\n\n// ==== Wash.java ====\npublic enum Wash {\n    BASIC, FOAM, DELUXE\n}\n","hints":["The labels are the constants' names alone: `case BASIC -> 4;`.","`return switch (wash) { ... };` with a case for each of the three constants compiles without a `default`, since nothing else is left."],"tests":[{"name":"System.out.println(washPrice(Wash.FOAM));","stdin":"","call":"System.out.println(washPrice(Wash.FOAM));\n","expect":"7","hidden":false},{"name":"System.out.println(washPrice(Wash.BASIC) + \" \" + washPrice(Wash.DELUXE));","stdin":"","call":"System.out.println(washPrice(Wash.BASIC) + \" \" + washPrice(Wash.DELUXE));\n","expect":"4 12","hidden":true},{"name":"Test 3","stdin":"","call":"int total = 0;\nfor (Wash w : Wash.values()) {\n    total += washPrice(w) * 3;\n}\nSystem.out.println(total);\n","expect":"69","hidden":true}],"require":[{"pattern":"\\bswitch\\s*\\(\\s*wash\\s*\\)","message":"Choose the price with `switch (wash)`."}],"forbid":[{"pattern":"\\bdefault\\b","message":"Leave out `default`: cover each of the three constants with its own case, so that javac tells you when a new one is added."},{"pattern":"\\bif\\s*\\(","message":"Use the switch instead of `if`."}]},{"task":"A puzzle game sets the player's lives and time limit by the difficulty. The designer has just added a fourth difficulty, `EXPERT`, to the enum in `Difficulty.java`, and now `Main.java` doesn't compile: javac says that the switch expressions don't cover all possible input values. Press Run to see it. Fix both methods: an expert game gives 1 life and 60 seconds. Don't add a `default`. The check runs:\n\n```java\nSystem.out.println(\"Lives: \" + startingLives(Difficulty.EXPERT) + \", time: \" + timeLimit(Difficulty.EXPERT) + \" seconds\");\n```\n\nwhich prints:\n\n```\nLives: 1, time: 60 seconds\n```\n","kind":"code","seed":"// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        for (Difficulty difficulty : Difficulty.values()) {\n            System.out.println(difficulty + \": lives \" + startingLives(difficulty) + \", time \" + timeLimit(difficulty) + \" seconds\");\n        }\n    }\n\n    public static int startingLives(Difficulty difficulty) {\n        return switch (difficulty) {\n            case EASY -> 5;\n            case NORMAL -> 3;\n            case HARD -> 2;\n        };\n    }\n\n    public static int timeLimit(Difficulty difficulty) {\n        return switch (difficulty) {\n            case EASY, NORMAL -> 300;\n            case HARD -> 180;\n        };\n    }\n}\n\n// ==== Difficulty.java ====\npublic enum Difficulty {\n    EASY, NORMAL, HARD, EXPERT\n}\n","solution":"// ==== Main.java ====\npublic class Main {\n    public static void main(String[] args) {\n        for (Difficulty difficulty : Difficulty.values()) {\n            System.out.println(difficulty + \": lives \" + startingLives(difficulty) + \", time \" + timeLimit(difficulty) + \" seconds\");\n        }\n    }\n\n    public static int startingLives(Difficulty difficulty) {\n        return switch (difficulty) {\n            case EASY -> 5;\n            case NORMAL -> 3;\n            case HARD -> 2;\n            case EXPERT -> 1;\n        };\n    }\n\n    public static int timeLimit(Difficulty difficulty) {\n        return switch (difficulty) {\n            case EASY, NORMAL -> 300;\n            case HARD -> 180;\n            case EXPERT -> 60;\n        };\n    }\n}\n\n// ==== Difficulty.java ====\npublic enum Difficulty {\n    EASY, NORMAL, HARD, EXPERT\n}\n","hints":["javac names two lines, one per switch expression. Each is missing a case for the new constant.","Add `case EXPERT -> 1;` to `startingLives` and `case EXPERT -> 60;` to `timeLimit`."],"tests":[{"name":"System.out.println(\"Lives: \" + startingLives(Difficulty.EXPERT) + \", time: \" + timeLimit(Difficulty.EXPERT) + \" seconds\");","stdin":"","call":"System.out.println(\"Lives: \" + startingLives(Difficulty.EXPERT) + \", time: \" + timeLimit(Difficulty.EXPERT) + \" seconds\");\n","expect":"Lives: 1, time: 60 seconds","hidden":false},{"name":"Test 2","stdin":"","expect":"EASY: lives 5, time 300 seconds\nNORMAL: lives 3, time 300 seconds\nHARD: lives 2, time 180 seconds\nEXPERT: lives 1, time 60 seconds","hidden":true},{"name":"System.out.println(startingLives(Difficulty.HARD) + timeLimit(Difficulty.NORMAL));","stdin":"","call":"System.out.println(startingLives(Difficulty.HARD) + timeLimit(Difficulty.NORMAL));\n","expect":"302","hidden":true}],"require":[],"forbid":[{"pattern":"\\bdefault\\b","message":"Don't add `default`: give EXPERT its own case in each switch."}]}]}