{"id":"switch-and-text-text-blocks","slug":"text-blocks-and-format","title":"Text blocks and formatted text","text":"### Text blocks\n\nText of several lines, such as a ticket, has so far been many `println` calls, or one String full of `\\n`. Since Java 15, a **text block** holds text of several lines as it looks. It starts with three double quotes and a line break, and ends with three double quotes:\n\n```java\nString ticket = \"\"\"\n        FERRY TICKET\n        From: Harbor A\n        To:   Island Pier\n          Deck seat 14\n        \"\"\";\nSystem.out.print(ticket);\n```\n\n```output\nFERRY TICKET\nFrom: Harbor A\nTo:   Island Pier\n  Deck seat 14\n```\n\n- The text starts on the line after the opening `\"\"\"`. Nothing but spaces may follow the opening quotes on their line.\n- The lines are indented to fit the code, and javac removes that indentation: it finds the line with the fewest spaces in front (lines with nothing else on them don't count), counting the line of the closing `\"\"\"` too, and removes that many spaces from every line. Here the closing quotes are as far in as `FERRY TICKET`, so `FERRY TICKET` starts at the left edge, and `Deck seat 14`, two spaces further in, keeps its two spaces.\n- Since the closing `\"\"\"` is on a line of its own, the text ends with a line break, after `Deck seat 14`. That's why the example uses `print`: `println` would add a second line break, an empty line. Write the closing quotes right after the last word, `Deck seat 14\"\"\";`, for a text block without that last line break.\n- A double quote inside a text block needs no backslash: `Seat \"14\"` can be written as it is. `\\n` and `\\t` still work, a backslash at the very end of a line joins it with the next one, and spaces at the end of a line are removed (write `\\s` for a space that must stay).\n\nText on the line of the opening quotes doesn't compile:\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        String sign = \"\"\"Closed\n            today\"\"\";\n        System.out.println(sign);\n    }\n}\n```\n\n```javac\nMain.java:3: error: illegal text block open delimiter sequence, missing line terminator\n        String sign = \"\"\"Closed\n                         ^\nMain.java:4: error: illegal text block open delimiter sequence, missing line terminator\n            today\"\"\";\n                    ^\n2 errors\n```\n\nA text block is an ordinary String once it's made: `ticket.length()`, `ticket.split(\"\\n\")` and the rest work as always.\n\n### String.format\n\nBuilding a line from text and values with `+` gets hard to read when there are many of them. `String.format` takes a **format**, a String with places marked by `%`, and the values to put there, in order:\n\n```java\nString name = \"Ada\";\nint seat = 14;\ndouble price = 23.5;\nString line = String.format(\"%s sits in seat %d and paid %.2f euros\", name, seat, price);\nSystem.out.println(line);\nSystem.out.printf(\"Seat %d of %d%n\", seat, 120);\n```\n\n```output\nAda sits in seat 14 and paid 23.50 euros\nSeat 14 of 120\n```\n\n- `%s` puts a value as text (any value: a String, a number, an object's `toString`), `%d` a whole number, and `%f` a decimal number. `%.2f` rounds it to two decimals.\n- `System.out.printf` formats and prints in one go. Unlike `println`, it adds no line break: `%n` in the format is one.\n- Since Java 15, a String has the method `formatted`, which does the same with the String as the format: `\"Seat %d\".formatted(seat)`. It goes well with a text block: write the text block with `%s` and `%d` in it, and call `.formatted(...)` on it.\n- `%%` prints a percent sign.\n- The decimal point follows the language settings of the computer: on Java Arena it's a point, but `String.format(\"%.2f\", 23.5)` gives `23,50` on a computer set up for a language that writes a comma, such as German or Finnish.\n\nA value of the wrong kind for its place is an error only when the program runs: `%d` with a double crashes.\n\n```java\npublic class Main {\n    public static void main(String[] args) {\n        double weight = 2.5;\n        System.out.println(String.format(\"%d kg\", weight));\n    }\n}\n```\n\n```crash\nException in thread \"main\" java.util.IllegalFormatConversionException: d != java.lang.Double\n\tat Main.main(Main.java:4)\n```\n\n### Columns\n\nA number between the `%` and the letter is a **width**: the value takes at least that many characters, padded with spaces in front, so that it's right-aligned. A minus sign pads after the value instead, which aligns it left. `%8.2f` is a decimal number with two decimals in eight characters, `%-10s` a text left-aligned in ten, and `%03d` pads a whole number with zeros instead of spaces:\n\n```java\nSystem.out.println(String.format(\"%-10s|%8.2f|\", \"tea\", 3.5));\nSystem.out.println(String.format(\"%-10s|%8.2f|\", \"cake\", 12.25));\nSystem.out.println(String.format(\"Locker %03d\", 7));\n```\n\n```output\ntea       |    3.50|\ncake      |   12.25|\nLocker 007\n```\n\nEvery line has its name in the same ten characters and its price in the same eight, so the columns line up, whatever the lengths. A value longer than its width isn't cut: it just takes more room.\n","task":"An ice rink prints a sign for its skate nights. Print this sign with one text block and one `System.out.print` or `System.out.println`. Watch the indentation: the second and third lines start with four spaces.\n\n```\n*** SKATE NIGHT ***\n    Friday at 19:00\n    Rink B\nSkates for rent at the desk\n```\n","kind":"code","seed":"public class Main {\n    public static void main(String[] args) {\n        String sign = \"\";\n        System.out.print(sign);\n    }\n}\n","solution":"public class Main {\n    public static void main(String[] args) {\n        String sign = \"\"\"\n                *** SKATE NIGHT ***\n                    Friday at 19:00\n                    Rink B\n                Skates for rent at the desk\n                \"\"\";\n        System.out.print(sign);\n    }\n}\n","hints":["Start the text block with `\"\"\"` at the end of the line `String sign = \"\"\"`, and write the lines of the sign on the lines after it.","javac removes the indentation that the least indented line has, counting the closing quotes. Put the closing `\"\"\"` as far in as `*** SKATE NIGHT ***`, and the lines with `Friday` and `Rink B` four spaces further."],"tests":[{"name":"Output","stdin":"","expect":"*** SKATE NIGHT ***\n    Friday at 19:00\n    Rink B\nSkates for rent at the desk","hidden":false}],"require":[{"pattern":"\"\"\"","raw":true,"message":"Write the sign in a text block, between `\"\"\"` and `\"\"\"`."},{"pattern":"System\\.out\\.print","max":1,"message":"Print the sign with one `print` or `println`."}],"forbid":[{"pattern":"\\\\n","raw":true,"message":"Let the text block's own line breaks make the lines, without `\\n`."}],"more":[{"task":"A dice game shows a line after each round. Write the method `roundLine(String player, int points, int round)`, which returns a line like `Round 03: Mia scored 12 points`: the round with at least two digits, padded with a zero in front, the player and the points. Build it with `String.format` (or `formatted`), without `+`. The check runs `System.out.println(roundLine(\"Mia\", 12, 3));`, which prints:\n\n```\nRound 03: Mia scored 12 points\n```\n","kind":"code","seed":"public class Main {\n    public static void main(String[] args) {\n        System.out.println(roundLine(\"Mia\", 12, 3));\n    }\n\n    public static String roundLine(String player, int points, int round) {\n        return \"\";\n    }\n}\n","solution":"public class Main {\n    public static void main(String[] args) {\n        System.out.println(roundLine(\"Mia\", 12, 3));\n    }\n\n    public static String roundLine(String player, int points, int round) {\n        return String.format(\"Round %02d: %s scored %d points\", round, player, points);\n    }\n}\n","hints":["The values fill the places in order, so the round comes first: `String.format(\"Round %02d: ...\", round, ...)`.","`%02d` is a whole number in at least two characters, padded with zeros: 3 becomes `03`, 12 stays `12`. Then `%s` for the player and `%d` for the points."],"tests":[{"name":"System.out.println(roundLine(\"Mia\", 12, 3));","stdin":"","call":"System.out.println(roundLine(\"Mia\", 12, 3));\n","expect":"Round 03: Mia scored 12 points","hidden":false},{"name":"System.out.println(roundLine(\"Jonas\", 0, 10));","stdin":"","call":"System.out.println(roundLine(\"Jonas\", 0, 10));\n","expect":"Round 10: Jonas scored 0 points","hidden":true},{"name":"System.out.println(roundLine(\"Li\", 105, 1));","stdin":"","call":"System.out.println(roundLine(\"Li\", 105, 1));\n","expect":"Round 01: Li scored 105 points","hidden":true},{"name":"System.out.println(roundLine(\"Ana\", 7, 123));","stdin":"","call":"System.out.println(roundLine(\"Ana\", 7, 123));\n","expect":"Round 123: Ana scored 7 points","hidden":true}],"require":[{"pattern":"String\\.format\\s*\\(|\\.formatted\\s*\\(","message":"Build the line with `String.format(...)` or `formatted(...)`."}],"forbid":[{"pattern":"\\+","message":"Let the format put the values in place, without `+`."}]},{"task":"A snack bar prints receipts. The program reads lines, each an item, its price and a quantity separated by spaces, such as `coffee 2.80 2`, until the line `end`. For each item it prints the name left-aligned in 10 characters and the item's total (price times quantity) right-aligned in 8 characters with two decimals. Then it prints a line of 18 minus signs, and `TOTAL` with the sum of the totals in the same two columns. With this input:\n\n```input\ncoffee 2.80 2\nbagel 3.25 1\njuice 4.10 3\nend\n```\n\nthe program prints:\n\n```\ncoffee        5.60\nbagel         3.25\njuice        12.30\n------------------\nTOTAL        21.15\n```\n","kind":"code","seed":"import java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        var scanner = new Scanner(System.in);\n\n    }\n}\n","solution":"import java.util.Scanner;\n\npublic class Main {\n    public static void main(String[] args) {\n        var scanner = new Scanner(System.in);\n        var sum = 0.0;\n        while (true) {\n            var line = scanner.nextLine();\n            if (line.equals(\"end\")) {\n                break;\n            }\n            var parts = line.split(\" \");\n            var price = Double.valueOf(parts[1]);\n            var quantity = Integer.valueOf(parts[2]);\n            var total = price * quantity;\n            sum += total;\n            System.out.println(String.format(\"%-10s%8.2f\", parts[0], total));\n        }\n        System.out.println(\"------------------\");\n        System.out.println(String.format(\"%-10s%8.2f\", \"TOTAL\", sum));\n    }\n}\n","hints":["Split each line with `split(\" \")`: the name, the price (`Double.valueOf`) and the quantity (`Integer.valueOf`). Keep a running sum of the totals.","`String.format(\"%-10s%8.2f\", name, total)` makes one line of the receipt, and the TOTAL line uses the same format."],"tests":[{"name":"Test 1","stdin":"coffee 2.80 2\nbagel 3.25 1\njuice 4.10 3\nend\n","expect":"coffee        5.60\nbagel         3.25\njuice        12.30\n------------------\nTOTAL        21.15","hidden":false},{"name":"Test 2","stdin":"cinnamon 3.90 4\ntea 2.10 1\nend\n","expect":"cinnamon     15.60\ntea           2.10\n------------------\nTOTAL        17.70","hidden":true},{"name":"Test 3","stdin":"cheesecake 5.45 12\nwater 1.20 3\nscone 2.65 2\nend\n","expect":"cheesecake   65.40\nwater         3.60\nscone         5.30\n------------------\nTOTAL        74.30","hidden":true},{"name":"Test 4","stdin":"end\n","expect":"------------------\nTOTAL         0.00","hidden":true}],"require":[{"pattern":"%-10s","raw":true,"message":"Left-align the names with `%-10s`."},{"pattern":"%8\\.2f","raw":true,"message":"Right-align the amounts with `%8.2f`."}],"forbid":[]}]}