openjdk.ruOpenJDK на русском

JEP 378: Text Blocks

Text Blocks (текстовые блоки)

ОтветственныйJim Laskey
ТипFeature
ОбластьSE
СтатусClosed / Delivered
Выпуск15
Компонентspecification / language
Обсуждениеjdk dash dev at openjdk dot java dot net
ТрудоёмкостьM
ДлительностьM
Связан сJEP 355: Text Blocks (Preview)
JEP 368: Text Blocks (Second Preview)
РецензентыAlex Buckley
ОдобренMark Reinhold
Создан2020/01/10 15:18
Обновлён2026/02/10 15:42
Задача8236934

Аннотация

Добавить в язык Java Text Blocks. Литерал Text Blocks — это многострочный строковый литерал, который избавляет от необходимости в большинстве escape-последовательностей, автоматически форматирует строку предсказуемым образом и при необходимости даёт разработчику контроль над форматом.

История

Text Blocks были предложены в JEP 355 в начале 2019 года как продолжение исследований, начатых в JEP 326 (Raw String Literals). Тот JEP изначально планировался в JDK 12, но в итоге был отозван и не вошёл в этот выпуск. JEP 355 был запланирован в JDK 13 в июне 2019 года как возможность в статусе Preview (предварительная версия). По отзывам о JDK 13 Text Blocks следовало снова выпустить в статусе Preview в JDK 14, добавив две новые escape-последовательности. Поэтому JEP 368 был запланирован в JDK 14 в ноябре 2019 года как Preview-возможность. По отзывам о JDK 14 Text Blocks были готовы стать окончательной и постоянной возможностью в JDK 15 без дальнейших изменений.

Цели

  • Упростить написание программ на Java: сделать так, чтобы строки, занимающие несколько строк исходного кода, записывались легко и в типичных случаях без escape-последовательностей.

  • Улучшить читаемость строк в программах на Java, которые содержат код на других языках.

  • Поддержать переход со строковых литералов: любая новая конструкция должна выражать тот же набор строк, что и строковый литерал, интерпретировать те же escape-последовательности и допускать те же операции, что и строковый литерал.

  • Добавить escape-последовательности для явного управления пробельными символами и переводами строк.

Что не является целью

  • Цель не в том, чтобы определить новый ссылочный тип, отличный от java.lang.String, для строк, выражаемых какой-либо новой конструкцией.

  • Цель не в том, чтобы определить новые операторы, отличные от +, которые принимают операнды String.

  • Text Blocks не поддерживают интерполяцию строк напрямую. Интерполяция может быть рассмотрена в одном из будущих JEP. Пока же в ситуациях, где могла бы понадобиться интерполяция, помогает новый метод экземпляра String::formatted.

  • Text Blocks не поддерживают «сырые» строки, то есть строки, символы которых никак не обрабатываются.

Мотивация

Чтобы в Java встроить фрагмент HTML, XML, SQL или JSON в строковый литерал "...", обычно приходится основательно отредактировать его, добавив escape-последовательности и конкатенацию, прежде чем код с этим фрагментом скомпилируется. Такой фрагмент часто трудно читать и тяжело сопровождать.

В более общем смысле потребность записывать в программе на Java короткие, средние и длинные блоки текста возникает почти везде, будь то код на других языках программирования, структурированный текст эталонных файлов (golden files) или сообщения на естественных языках. С одной стороны, язык Java признаёт эту потребность, допуская строки неограниченного размера и содержания. С другой стороны, в нём заложено исходное проектное допущение, что строки достаточно малы, чтобы записываться в одной строке исходного файла (в окружении символов "), и достаточно просты, чтобы их было легко экранировать. Это допущение противоречит большому числу программ на Java, где строки слишком длинные, чтобы удобно помещаться в одной строке.

Поэтому языковой механизм для записи строк более буквально, чем строковым литералом, — в несколько строк и без визуального шума escape-последовательностей — улучшил бы и читаемость, и удобство написания широкого класса программ на Java. По сути, это двумерный блок текста, а не одномерная последовательность символов.

Тем не менее предсказать роль каждой строки в программах на Java невозможно. Из того, что строка занимает несколько строк исходного кода, не следует, что в ней нужны символы перевода строки. Одна часть программы может читаться лучше, если строки разбиты на несколько строк, но встроенные символы перевода строки могут изменить поведение другой части программы. Поэтому было бы полезно, чтобы разработчик точно управлял тем, где появляются переводы строк, и, в связи с этим, сколько пробельных символов стоит слева и справа от «блока» текста.

Пример с 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>
              """;

Пример с SQL

С «одномерными» строковыми литералами

String query = "SELECT \"EMP_ID\", \"LAST_NAME\" FROM \"EMPLOYEE_TB\"\n" +
               "WHERE \"CITY\" = 'INDIANAPOLIS'\n" +
               "ORDER BY \"EMP_ID\", \"LAST_NAME\";\n";

С «двумерным» блоком текста

String query = """
               SELECT "EMP_ID", "LAST_NAME" FROM "EMPLOYEE_TB"
               WHERE "CITY" = 'INDIANAPOLIS'
               ORDER BY "EMP_ID", "LAST_NAME";
               """;

Пример с другим языком программирования

С «одномерными» строковыми литералами

ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
Object obj = engine.eval("function hello() {\n" +
                         "    print('\"Hello, world\"');\n" +
                         "}\n" +
                         "\n" +
                         "hello();\n");

С «двумерным» блоком текста

ScriptEngine engine = new ScriptEngineManager().getEngineByName("js");
Object obj = engine.eval("""
                         function hello() {
                             print('"Hello, world"');
                         }
                         
                         hello();
                         """);

Описание

Этот раздел совпадает с тем же разделом предшественника этого JEP, JEP 355, за исключением добавленного подраздела о новых escape-последовательностях.

Литерал Text Blocks — это новый вид литерала в языке Java. Его можно использовать для записи строки везде, где может стоять строковый литерал, но он выразительнее и не несёт лишней случайной сложности.

Литерал Text Blocks состоит из нуля или более символов содержимого, заключённых между открывающим и закрывающим разделителями.

Открывающий разделитель — это последовательность из трёх символов двойной кавычки ("""), за которой следуют ноль или более пробельных символов и затем символ конца строки. Содержимое начинается с первого символа после символа конца строки открывающего разделителя.

Закрывающий разделитель — это последовательность из трёх символов двойной кавычки. Содержимое заканчивается на последнем символе перед первой двойной кавычкой закрывающего разделителя.

В отличие от символов строкового литерала, содержимое может напрямую включать символы двойной кавычки. Использовать \" в литерале Text Blocks разрешено, но не нужно и не рекомендуется. «Толстые» разделители (""") выбраны для того, чтобы символы " могли стоять без экранирования, а также чтобы визуально отличать литерал Text Blocks от строкового литерала.

В отличие от символов строкового литерала, содержимое может напрямую включать символы конца строки. Использовать \n в литерале Text Blocks разрешено, но не нужно и не рекомендуется. Например, литерал Text Blocks:

"""
line 1
line 2
line 3
"""

эквивалентен строковому литералу:

"line 1\nline 2\nline 3\n"

или конкатенации строковых литералов:

"line 1\n" +
"line 2\n" +
"line 3\n"

Если символ конца строки в конце строки не нужен, закрывающий разделитель можно поставить на последней строке содержимого. Например, литерал Text Blocks:

"""
line 1
line 2
line 3"""

эквивалентен строковому литералу:

"line 1\nline 2\nline 3"

Литерал Text Blocks может обозначать пустую строку, хотя это не рекомендуется, потому что для этого нужны две строки исходного кода:

String empty = """
""";

Вот несколько примеров некорректных литералов Text Blocks:

String a = """""";   // no line terminator after opening delimiter
String b = """ """;  // no line terminator after opening delimiter
String c = """
           ";        // no closing delimiter (text block continues to EOF)
String d = """
           abc \ def
           """;      // unescaped backslash (see below for escape processing)

Обработка во время компиляции

Литерал Text Blocks — это константное выражение типа String, как и строковый литерал. Однако, в отличие от строкового литерала, содержимое литерала Text Blocks обрабатывается компилятором Java в три отдельных шага:

  1. Символы конца строки в содержимом преобразуются в LF (\u000A). Цель этого преобразования — следовать принципу наименьшего удивления при переносе исходного кода на Java между платформами.

  2. Случайные пробельные символы вокруг содержимого, появившиеся из-за выравнивания по отступам исходного кода на Java, удаляются.

  3. Escape-последовательности в содержимом интерпретируются. Поскольку интерпретация выполняется последним шагом, разработчики могут писать escape-последовательности, например \n, и предыдущие шаги не изменят и не удалят их.

Обработанное содержимое записывается в файл class как элемент CONSTANT_String_info в пуле констант, так же как символы строкового литерала. Файл class не хранит сведений о том, получен ли элемент CONSTANT_String_info из литерала Text Blocks или из строкового литерала.

Во время выполнения литерал Text Blocks вычисляется в экземпляр String, как и строковый литерал. Экземпляры String, полученные из литералов Text Blocks, неотличимы от экземпляров, полученных из строковых литералов. Два литерала Text Blocks с одинаковым обработанным содержимым будут ссылаться на один и тот же экземпляр String благодаря интернированию, так же как и строковые литералы.

В следующих разделах обработка во время компиляции рассматривается подробнее.

1. Символы конца строки

Компилятор Java нормализует символы конца строки в содержимом: CR (\u000D) и CRLF (\u000D\u000A) превращаются в LF (\u000A). Благодаря этому строка, полученная из содержимого, одинакова на всех платформах, даже если исходный код был перекодирован в кодировку платформы (см. javac -encoding).

Например, если исходный код на Java, созданный на платформе Unix (где символ конца строки — LF), редактируется на платформе Windows (где символ конца строки — CRLF), то без нормализации содержимое стало бы на один символ длиннее для каждой строки. Любой алгоритм, рассчитывающий на то, что символ конца строки — LF, мог бы дать сбой, а любой тест, проверяющий равенство строк с помощью String::equals, не прошёл бы.

Escape-последовательности \n (LF), \f (FF) и \r (CR) во время нормализации не интерпретируются: обработка escape-последовательностей происходит позже.

2. Случайные пробельные символы

Литералы Text Blocks, показанные выше, читались легче, чем их аналоги из сцепленных строковых литералов. Но очевидная интерпретация содержимого литерала Text Blocks включала бы пробелы, добавленные для отступа встроенной строки, чтобы она аккуратно выравнивалась по открывающему разделителю. Вот пример с HTML, где точками показаны пробелы, добавленные разработчиком для отступа:

String html = """
..............<html>
..............    <body>
..............        <p>Hello, world</p>
..............    </body>
..............</html>
..............""";

Поскольку открывающий разделитель обычно стоит в той же строке, что и оператор или выражение, использующее литерал Text Blocks, то, что каждая строка начинается с 14 показанных пробелов, не имеет реального значения. Если включить эти пробелы в содержимое, литерал Text Blocks обозначал бы строку, отличную от той, которую обозначают сцепленные строковые литералы. Это затруднило бы переход и постоянно вызывало бы удивление: почти наверняка разработчику эти пробелы в строке не нужны. Кроме того, закрывающий разделитель обычно выравнивается по содержимому, что ещё раз указывает на незначимость этих 14 показанных пробелов.

Пробелы могут стоять и в конце каждой строки, особенно когда литерал Text Blocks заполняется фрагментами, скопированными из других файлов (которые сами могли быть составлены копированием из ещё каких-то файлов). Вот пример с HTML, переосмысленный с завершающими пробельными символами, где пробелы снова показаны точками:

String html = """
..............<html>...
..............    <body>
..............        <p>Hello, world</p>....
..............    </body>.
..............</html>...
..............""";

Завершающие пробельные символы чаще всего появляются непреднамеренно, зависят от привычек автора и не имеют значения. Почти наверняка разработчику до них нет дела. Завершающие пробельные символы похожи на символы конца строки: и те и другие — невидимые артефакты среды редактирования исходного кода. Поскольку наличие завершающих пробельных символов визуально никак не заметно, их включение в содержимое постоянно вызывало бы удивление, ведь они влияли бы на длину строки, её хэш-код и т. д.

Поэтому правильная интерпретация содержимого литерала Text Blocks должна отличать случайные пробельные символы в начале и конце каждой строки от значимых пробельных символов. Компилятор Java обрабатывает содержимое, удаляя случайные пробельные символы, чтобы получить то, что имел в виду разработчик. Затем при желании можно дополнительно изменить отступы с помощью String::indent. Границы показаны символом |:

|<html>|
|    <body>|
|        <p>Hello, world</p>|
|    </body>|
|</html>|

Алгоритм перерасчёта отступов принимает содержимое литерала Text Blocks, в котором символы конца строки уже нормализованы в LF. Он удаляет одинаковое количество пробельных символов из каждой строки содержимого, пока хотя бы в одной из строк в крайней левой позиции не окажется непробельный символ. Положение открывающих символов """ на алгоритм не влияет, а положение закрывающих символов """ влияет, если они стоят на отдельной строке. Алгоритм выглядит так:

  1. Разбить содержимое литерала Text Blocks по каждому LF и получить список отдельных строк. Обратите внимание: любая строка содержимого, состоявшая только из LF, станет пустой строкой в списке отдельных строк.

  2. Добавить все непробельные строки из списка отдельных строк в множество определяющих строк. (Пробельные строки — пустые или целиком состоящие из пробельных символов — видимым образом не влияют на отступы. Если исключить пробельные строки из множества определяющих строк, они не нарушат шаг 4 алгоритма.)

  3. Если последняя строка в списке отдельных строк (т. е. строка с закрывающим разделителем) пробельная, добавить её в множество определяющих строк. (Отступ закрывающего разделителя должен влиять на отступ всего содержимого — правило значимой завершающей строки.)

  4. Вычислить общий пробельный префикс множества определяющих строк: подсчитать число начальных пробельных символов в каждой строке и взять наименьшее значение.

  5. Удалить общий пробельный префикс из каждой непробельной строки в списке отдельных строк.

  6. Удалить все завершающие пробельные символы из всех строк изменённого списка отдельных строк, полученного на шаге 5. На этом шаге строки изменённого списка, целиком состоящие из пробельных символов, становятся пустыми, но не отбрасываются.

  7. Построить итоговую строку: соединить все строки изменённого списка отдельных строк, полученного на шаге 6, с LF в качестве разделителя между строками. Если последняя строка в списке с шага 6 пустая, последним символом итоговой строки будет соединяющий LF от предыдущей строки.

Управляющие последовательности \b (backspace), \t (табуляция) и \s (пробел) алгоритм не интерпретирует: они обрабатываются позже. Точно так же управляющая последовательность \<line-terminator> не мешает разбивать строки по символу конца строки, поскольку до обработки управляющих последовательностей она считается двумя отдельными символами.

Алгоритм перерасчёта отступов будет нормативно закреплён в The Java Language Specification. Разработчики получат к нему доступ через новый метод экземпляра String::stripIndent.

Правило значимой завершающей строки

Обычно литерал Text Blocks форматируют двумя способами: во-первых, располагают левый край содержимого под первым символом " открывающего разделителя, а во-вторых, выносят закрывающий разделитель на отдельную строку точно под открывающим разделителем. В получившейся строке ни одна строка не будет начинаться с пробельных символов, и в неё не войдёт завершающая пробельная строка закрывающего разделителя.

Однако завершающая пробельная строка считается определяющей строкой, поэтому её сдвиг влево уменьшает общий пробельный префикс, а значит, и число пробельных символов, удаляемых в начале каждой строки. В крайнем случае, когда закрывающий разделитель сдвинут до упора влево, общий пробельный префикс уменьшается до нуля, и удаление пробельных символов фактически отключается.

Например, если закрывающий разделитель сдвинут до упора влево, случайных пробельных символов, которые можно было бы показать точками, нет:

String html = """
              <html>
                  <body>
                      <p>Hello, world</p>
                  </body>
              </html>
""";

С учётом завершающей пробельной строки с закрывающим разделителем общий пробельный префикс равен нулю, поэтому в начале строк не удаляется ни одного пробельного символа. Таким образом, алгоритм выдаёт следующее (левая граница показана символом |):

|              <html>
|                  <body>
|                      <p>Hello, world</p>
|                  </body>
|              </html>

Предположим теперь, что закрывающий разделитель сдвинут не до упора влево, а под t в html, то есть на восемь пробелов глубже объявления переменной:

String html = """
              <html>
                  <body>
                      <p>Hello, world</p>
                  </body>
              </html>
        """;

Пробелы, показанные точками, считаются случайными:

String html = """
........      <html>
........          <body>
........              <p>Hello, world</p>
........          </body>
........      </html>
........""";

С учётом завершающей пробельной строки с закрывающим разделителем общий пробельный префикс равен восьми, поэтому в начале каждой строки удаляется восемь пробельных символов. Таким образом, алгоритм сохраняет значимый отступ содержимого относительно закрывающего разделителя:

|      <html>
|          <body>
|              <p>Hello, world</p>
|          </body>
|      </html>

Наконец, предположим, что закрывающий разделитель сдвинут немного правее содержимого:

String html = """
              <html>
                  <body>
                      <p>Hello, world</p>
                  </body>
              </html>
                  """;

Пробелы, показанные точками, считаются случайными:

String html = """
..............<html>
..............    <body>
..............        <p>Hello, world</p>
..............    </body>
..............</html>
..............    """;

Общий пробельный префикс равен 14, поэтому в начале каждой строки удаляется 14 пробельных символов. После удаления пробельных символов завершающая строка становится пустой, а поскольку она последняя, затем отбрасывается. Иными словами, сдвиг закрывающего разделителя правее содержимого ни на что не влияет, и алгоритм снова сохраняет значимый отступ содержимого:

|<html>
|    <body>
|        <p>Hello, world</p>
|    </body>
|</html>

3. Управляющие последовательности

После перерасчёта отступов все управляющие последовательности в содержимом интерпретируются. Text Blocks поддерживают все управляющие последовательности, допустимые в строковых литералах, включая \n, \t, \', \" и \\. Полный список см. в разделе 3.10.6 The Java Language Specification. Разработчики получат доступ к обработке управляющих последовательностей через новый метод экземпляра String::translateEscapes.

Управляющие последовательности интерпретируются на последнем шаге. Поэтому разработчики могут использовать \n, \f и \r для вертикального форматирования строки, не затрагивая преобразование символов конца строки на шаге 1, а \b и \t — для горизонтального форматирования строки, не затрагивая удаление случайных пробельных символов на шаге 2. Рассмотрим, например, такой литерал Text Blocks с управляющей последовательностью \r (CR):

String html = """
              <html>\r
                  <body>\r
                      <p>Hello, world</p>\r
                  </body>\r
              </html>\r
              """;

Управляющие последовательности CR обрабатываются только после того, как символы конца строки нормализованы в LF. Если показать LF (\u000A) и CR (\u000D) управляющими последовательностями Unicode, результат будет таким:

|<html>\u000D\u000A
|    <body>\u000D\u000A
|        <p>Hello, world</p>\u000D\u000A
|    </body>\u000D\u000A
|</html>\u000D\u000A

Обратите внимание, что " и "" можно свободно использовать внутри литерала Text Blocks, кроме позиции непосредственно перед закрывающим разделителем. Например, следующие литералы Text Blocks допустимы:

String story = """
    "When I use a word," Humpty Dumpty said,
    in rather a scornful tone, "it means just what I
    choose it to mean - neither more nor less."
    "The question is," said Alice, "whether you
    can make words mean so many different things."
    "The question is," said Humpty Dumpty,
    "which is to be master - that's all."
    """;    // Note the newline before the closing delimiter

String code =
    """
    String empty = "";
    """;

Однако в последовательности из трёх символов " необходимо экранировать хотя бы один ", чтобы она не выглядела как закрывающий разделитель. (В последовательности из n символов " необходимо экранировать не меньше Math.floorDiv(n,3) из них.) Символ " непосредственно перед закрывающим разделителем тоже необходимо экранировать. Например:

String code = 
    """
    String text = \"""
        A text block inside a text block
    \""";
    """;

String tutorial1 =
    """
    A common character
    in Java programs
    is \"""";

String tutorial2 =
    """
    The empty string literal
    is formed from " characters
    as follows: \"\"""";

System.out.println("""
     1 "
     2 ""
     3 ""\"
     4 ""\""
     5 ""\"""
     6 ""\"""\"
     7 ""\"""\""
     8 ""\"""\"""
     9 ""\"""\"""\"
    10 ""\"""\"""\""
    11 ""\"""\"""\"""
    12 ""\"""\"""\"""\"
""");

Новые управляющие последовательности

Чтобы точнее управлять обработкой переводов строк и пробельных символов, мы вводим две новые управляющие последовательности.

Во-первых, управляющая последовательность \<line-terminator> явно подавляет вставку символа перевода строки.

Например, очень длинные строковые литералы часто разбивают на конкатенацию более коротких подстрок, а затем принудительно переносят получившееся строковое выражение на несколько строк:

String literal = "Lorem ipsum dolor sit amet, consectetur adipiscing " +
                 "elit, sed do eiusmod tempor incididunt ut labore " +
                 "et dolore magna aliqua.";

С управляющей последовательностью \<line-terminator> это можно записать так:

String text = """
                Lorem ipsum dolor sit amet, consectetur adipiscing \
                elit, sed do eiusmod tempor incididunt ut labore \
                et dolore magna aliqua.\
                """;

По той простой причине, что символьные и традиционные строковые литералы не допускают встроенных переводов строк, управляющая последовательность \<line-terminator> применима только к Text Blocks.

Во-вторых, новая управляющая последовательность \s просто преобразуется в один пробел (\u0020).

Управляющие последовательности преобразуются только после удаления случайных пробелов, поэтому \s может служить ограничителем, который не даёт удалить завершающие пробельные символы. В этом примере \s в конце каждой строки гарантирует, что длина каждой строки ровно шесть символов:

String colors = """
    red  \s
    green\s
    blue \s
    """;

Управляющую последовательность \s можно использовать в Text Blocks, традиционных строковых литералах и символьных литералах.

Конкатенация литералов Text Blocks

Литералы Text Blocks можно использовать везде, где можно использовать строковый литерал. Например, литералы Text Blocks и строковые литералы можно конкатенировать в любом сочетании:

String code = "public void print(Object o) {" +
              """
                  System.out.println(Objects.toString(o));
              }
              """;

Однако конкатенация с участием литерала Text Blocks может оказаться довольно громоздкой. Возьмём за отправную точку такой литерал Text Blocks:

String code = """
              public void print(Object o) {
                  System.out.println(Objects.toString(o));
              }
              """;

Предположим, его нужно изменить так, чтобы тип o брался из переменной. При конкатенации литерал Text Blocks с оставшимся кодом придётся начинать с новой строки. К сожалению, если просто вставить в программу перевод строки, как показано ниже, между типом и текстом, начинающимся с o, появится длинный промежуток из пробельных символов:

String code = """
              public void print(""" + type + """
                                                 o) {
                  System.out.println(Objects.toString(o));
              }
              """;

Пробельные символы можно удалить вручную, но это ухудшает читаемость кода в кавычках:

String code = """
              public void print(""" + type + """
               o) {
                  System.out.println(Objects.toString(o));
              }
              """;

Более аккуратная альтернатива — использовать String::replace или String::format, например так:

String code = """
              public void print($type o) {
                  System.out.println(Objects.toString(o));
              }
              """.replace("$type", type);
String code = String.format("""
              public void print(%s o) {
                  System.out.println(Objects.toString(o));
              }
              """, type);

Другая альтернатива предполагает новый метод экземпляра String::formatted, который можно было бы использовать так:

String source = """
                public void print(%s object) {
                    System.out.println(Objects.toString(object));
                }
                """.formatted(type);

Дополнительные методы

Для поддержки Text Blocks будут добавлены следующие методы:

  • String::stripIndent(): удаляет случайные пробельные символы из содержимого литерала Text Blocks
  • String::translateEscapes(): преобразует управляющие последовательности
  • String::formatted(Object... args): упрощает подстановку значений в литерал Text Blocks

Альтернативы

Ничего не делать

Java успешно развивалась более 20 лет со строковыми литералами, в которых переводы строк приходилось экранировать. IDE облегчают сопровождение: они поддерживают автоматическое форматирование и конкатенацию строк, занимающих несколько строк исходного кода. Класс String тоже развивался, и в него вошли методы, упрощающие обработку и форматирование длинных строк, например метод, представляющий строку как поток строк. Однако строки — настолько фундаментальная часть языка Java, что недостатки строковых литералов очевидны огромному числу разработчиков. Другие языки для JVM тоже продвинулись в записи длинных и сложных строк. Поэтому неудивительно, что многострочные строковые литералы неизменно входят в число самых востребованных возможностей Java. Введение многострочной конструкции низкой или умеренной сложности дало бы большую отдачу.

Разрешить строковому литералу занимать несколько строк

Многострочные строковые литералы можно было бы ввести в Java, просто разрешив символы конца строки в существующих строковых литералах. Однако это никак не избавило бы от мучений с экранированием символов ". \" — самая частая управляющая последовательность после \n, поскольку фрагменты кода встречаются очень часто. Единственный способ не экранировать " в строковом литерале — предусмотреть для строковых литералов альтернативную схему разделителей. Разделители подробно обсуждались в JEP 326 (Raw String Literals), и извлечённые уроки учтены при проектировании Text Blocks, поэтому нарушать стабильность строковых литералов было бы ошибкой.

Заимствовать многострочный строковый литерал из другого языка

По словам Brian Goetz:

Многие предлагали заимствовать для Java многострочные строковые литералы из Swift или Rust. Однако подход «просто сделать как в языке X» безответственен по своей сути: почти каждая возможность каждого языка обусловлена другими возможностями этого языка. Задача в другом: учиться на том, как это сделано в других языках, оценивать выбранные ими компромиссы (явные и неявные) и спрашивать, что из этого применимо с учётом ограничений нашего языка и ожиданий пользователей в нашем сообществе.

Для JEP 326 (Raw String Literals) мы изучили многие современные языки программирования и то, как в них поддерживаются многострочные строковые литералы. Результаты этих исследований повлияли на текущее предложение, например на выбор трёх символов " в качестве разделителей (хотя у этого выбора были и другие причины) и на признание необходимости автоматически управлять отступами.

Не удалять случайные пробельные символы

Если бы в Java появились многострочные строковые литералы без поддержки автоматического удаления случайных пробельных символов, многие разработчики писали бы метод для их удаления сами или добивались бы, чтобы такой метод добавили в класс String. Однако это означает потенциально дорогое вычисление при каждом создании экземпляра строки во время выполнения, что уменьшило бы выгоду от интернирования строк. Наиболее подходящим решением представляется, чтобы язык Java обязывал удалять случайные пробельные символы, как в начале, так и в конце строк. Разработчики могут отказаться от удаления начальных пробельных символов, аккуратно расположив закрывающий разделитель.

Необработанные строковые литералы

Для JEP 326 (Raw String Literals) мы подошли к задаче записи строк без экранирования переводов строк и кавычек иначе, сосредоточившись на необработанности строк. Теперь мы считаем, что этот акцент был ошибочным: хотя необработанные строковые литералы легко могли занимать несколько строк исходного кода, цена поддержки неэкранированных разделителей в их содержимом была чрезмерной. Это снижало эффективность возможности в многострочном сценарии, а он критически важен, поскольку многострочные (но не по-настоящему необработанные) фрагменты кода часто встраиваются в программы на Java. Хорошим результатом смены акцента с необработанности на многострочность стало новое внимание к единому языку экранирования для строковых литералов, Text Blocks и связанных возможностей, которые могут быть добавлены в будущем.

Тестирование

Тесты, которые используют строковые литералы для создания, интернирования и обработки экземпляров String, следует продублировать, чтобы они использовали и Text Blocks. Следует добавить негативные тесты для граничных случаев с символами конца строки и EOF.

Следует добавить тесты, проверяющие, что в Text Blocks можно встраивать Java в Java, Markdown в Java, SQL в Java и как минимум один JVM-язык в Java.