Текстовые блоки в Java

Введение
Текстовый блок представляет собой многострочный строковый литерал следующего формата:
String block = """
        line 1
        line 2
        line 3
        """;
До его появления Java-программисты использовали громоздкие строковые литералы:
String literal = "line 1\nline 2\nline 3\n";

или

String literal = "line 1\n" +
        "line 2\n" +
        "line 3\n";
Если обычная строка обрамляется двойными кавычками, то в текстовом блоке используется последовательность из трех двойных кавычек """. При этом его содержимое должно начинаться с новой строки (под открывающимися """).
Если содержимое блока не должно заканчиваться переносом на новую строку, то закрывающие """ можно разместить в конце текста:
String block = """
        line 1
        line 2
        line 3""";

// Эквивалентный строковый литерал:
String literal = "line 1\nline 2\nline 3";
Еще примеры:
// Ошибка
String block = """Pat Q. Smith""";

// Ошибка
String block = """red
                 green
                 blue
                 """;

// OK - с переносом на новую строку
String block = """
        red
        green
        blue
        """;

// OK - без переноса на новую строку
String block = """
        red
        green
        blue""";
Если сравнить строковый литерал и текстовый блок, то читабельность последнего заметно лучше: он выглядит чище, поскольку лишен конкатенации и \n; в нем меньше синтаксического шума, перегружающего текст.
Целью создания текстовых блоков было желание разработчиков языка повысить читабельность многострочных литералов, в особенности содержащих код, написанный на SQL, XML, JSON и т. д. Именно такое содержимое обычно требует значительных затрат на форматирование, экранирование и конкатенацию.
Пример с HTML
// Стандартная строка
String html = "<html>\n" +
        "    <body>\n" +
        "        <p>Hello, world</p>\n" +
        "    </body>\n" +
        "</html>\n";

// Текстовый блок
String html = """
        <html>
            <body>
                <p>Hello, world</p>
            </body>
        </html>
        """;
1. Escape-последовательности
Наиболее часто используемым экранирующим символом в строковых литералах является перенос строки (\n), который больше не требуется в текстовых блоках. Следующим по популярности идет символ двойная кавычка ("), которую необходимо экранировать в строковых литералах, чего не требуется в текстовых блоках.
Текстовые блоки поддерживают все escape-последовательности, используемые в строковых литералах.
Кроме стандартного набора, текстовые блоки имеют две свои последовательности:
1
\ (line-terminator) — индикатор продолжения строки
Распространенной практикой является разбиение длинных строковых литералов на конкатенации более мелких подстрок (для удобства):
String literal = "Lorem ipsum dolor sit amet, consectetur adipiscing " +
        "elit, sed do eiusmod tempor incididunt ut labore " +
        "et dolore magna aliqua.";
С помощью \ и текстового блока этот литерал записывается так:
String block = """
        Lorem ipsum dolor sit amet, consectetur adipiscing \
        elit, sed do eiusmod tempor incididunt ut labore \
        et dolore magna aliqua.""";
Текст в консоли отобразится в одну строку, поскольку \ подавляет перенос:
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
2
\s (space) — для явного пробела
Поскольку после текстового блока все пробелы удаляются автоматически во время компиляции, то для их принудительного установления, следует использовать \s, которая соответствует одному пробелу.
Эта последовательность также может использоваться в качестве ограждения, предотвращая удаление завершающих пробелов.
Использование \s в этом примере гарантирует, что каждая строка будет иметь длину ровно шесть символов:
String colors = """
        red  \s
        green\s
        blue \s
        """;
2. Отступы в текстовых блоках
В текстовых блоках отступы удаляются компилятором не только после, но и перед блоком.
И если их требуется добавить внутри блока, то это следует делать относительно положения закрывающихся """ .
Например, пусть требуется, чтобы весь блок был с отступом в четыре пробела. Для этого следует сдвинув вправо текст блока на четыре пробела относительно закрывающего разделителя """:
void foo() {
    System.out.println("""
                <person>
                    <firstName>Bob</firstName>
                    <lastName>Jones</lastName>
                </person>
            """);
}
Кроме того, внутри текста средние строки тоже сдвигаются вправо на четыре пробела.
Вывод (точками показаны отступы):
....<person>
    ....<firstName>Bob</firstName>
    ....<lastName>Jones</lastName>
....</person>
Управлять отступами также можно с помощью метода String.indent(int n), который сдвигает каждую строку на фиксированное количество пробелов:
void m() {
    System.out.println("""
            <person>
                <firstName>Bob</firstName>
                <lastName>Jones</lastName>
            </person>
            """.indent(4));
}
3. Форматированный вывод
В Java строки не поддерживают интерполяцию (подстановку значений переменных внутрь текста), поэтому для их параметризации необходимо использовать специальные методы.
Поскольку текстовый блок в конечном итоге является обычной строкой, мы можем использовать String.format или String.formatted:
String person = """
        <person>
            <firstName>%s</firstName>
            <lastName>%s</lastName>
        </person>
        """.formatted(first, last);
Метод formatted() хорошо использовать, когда нужно присвоить строковый литерал переменной или вернуть его значение из метода:
String foo() {
    return """
            <person>
                <firstName>%s</firstName>
                <lastName>%s</lastName>
            </person>
            """.formatted(first, last);
}
Если требуется вывод, то для этого можно использовать printf:
System.out.printf("""
        Привет, %s
        Текущая версия - %d
        Быть добру
        """, "Java", 24);
4. Рекомендации по стилю
1
Используйте текстовые блоки, когда это улучшает ясность кода
Если строковый литерал содержит конкатенацию, экранированные символы и состоит из более одной строки, то желательно использовать текстовый блок. В противном случае прибегните к традиционным строковым литералам
2
Избегайте встроенных текстовых блоков в сложных выражениях
Хоть текстовые блоки и являются строковыми выражениями и, следовательно, могут использоваться везде, где ожидается строка, вместо вложения их в сложные выражения, лучше вынести их в отдельную переменную:
String poem = new String(Files.readAllBytes(Paths.get("jabberwocky.txt")));
String middleVerses = Pattern.compile("\\n\\n")
        .splitAsStream(poem)
        .match(verse -> !"""
                ’Twas brillig, and the slithy toves
                Did gyre and gimble in the wabe;
                All mimsy were the borogoves,
                And the mome raths outgrabe.
                """.equals(verse))
        .collect(Collectors.joining("\n\n"));
Если поместить текстовый блок в отдельную переменную, программисту, читающему код, будет проще следить за ходом вычислений:
String firstLastVerse = """
        ’Twas brillig, and the slithy toves
        Did gyre and gimble in the wabe;
        All mimsy were the borogoves,
        And the mome raths outgrabe.
        """;
String poem = new String(Files.readAllBytes(Paths.get("jabberwocky.txt")));
String middleVerses = Pattern.compile("\\n\\n")
        .splitAsStream(poem)
        .match(verse -> !firstLastVerse.equals(verse))
        .collect(Collectors.joining("\n\n"));
3
Избегайте смешивания пробелов и табуляции в отступе текстового блока
Выравнивайте текстовые блоки относительно других строк кода. Поскольку случайные пробелы автоматически удаляются, мы должны воспользоваться этим, чтобы облегчить чтение кода. Хотя мы могли бы написать:
void printPoem() {
    String poem = """
’Twas brillig, and the slithy toves
Did gyre and gimble in the wabe;
All mimsy were the borogoves,
And the mome raths outgrabe.
""";
    System.out.print(poem);
Поскольку мы не хотим, чтобы в наших строках были начальные отступы, большую часть времени мы должны писать так, чтобы уменьшить когнитивную нагрузку на программиста:
void printPoem() {
    String poem = """
            ’Twas brillig, and the slithy toves
            Did gyre and gimble in the wabe;
            All mimsy were the borogoves,
            And the mome raths outgrabe.
            """;
    System.out.print(poem);
}
4
Не выравнивайте текст по открывающему разделителю
Мы можем выбрать выравнивание содержимого текстового блока по открывающему разделителю:
String poem = """
              ’Twas brillig, and the slithy toves
              Did gyre and gimble in the wabe;
              All mimsy were the borogoves,
              And the mome raths outgrabe.
              """
Это может показаться привлекательным, но может быть громоздким, если строки длинные или разделитель начинается далеко от левого края. Но эта форма отступа не требуется — чаще всего вам придется писать так:
String poem = """
        ’Twas brillig, and the slithy toves
        Did gyre and gimble in the wabe;
        All mimsy were the borogoves,
        And the mome raths outgrabe.
        """;
Заключение
Строковые литералы в программах Java не ограничиваются короткими строками, такими как «да» и «нет». Они часто соответствуют целым «программам» в структурированных языках, таких как HTML, SQL, XML, JSON или даже Java. Возможность сохранения двумерной структуры этой встроенной программы без необходимости переписывать её с помощью escape-символов и других языковых вкраплений менее подвержена ошибкам и приводит к более читаемому коду.
Автор: Чимаев Максим
Оцените статью, если она вам понравилась!