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

JEP 368: Text Blocks (Second Preview)

Text Blocks (текстовые блоки), вторая версия Preview (предварительная версия)

ОтветственныйJim Laskey
ТипFeature
ОбластьSE
СтатусClosed / Delivered
Выпуск14
Компонентspecification / language
Обсуждениеamber dash dev at openjdk dot java dot net
ТрудоёмкостьM
ДлительностьM
Связан сJEP 355: Text Blocks (Preview)
JEP 378: Text Blocks
РецензентыAlex Buckley
Создан2019/09/30 14:10
Обновлён2021/08/28 00:19
Задача8231623

Аннотация

Добавить в язык Java Text Blocks. Блок Text Blocks — это многострочный строковый литерал, в котором не нужно большинство управляющих последовательностей. Он автоматически форматирует строку предсказуемым образом и при необходимости даёт разработчику контроль над форматом. Это возможность языка в статусе Preview в JDK 14.

История

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

Цели

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

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

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

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

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

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

  • Не ставится цель определить новые операторы, отличные от +, которые принимают операнды типа String.

  • Text Blocks не поддерживают интерполяцию строк напрямую. Интерполяция может быть рассмотрена в одном из будущих JEP.

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

Мотивация

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

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

Поэтому читаемость и удобство написания широкого класса программ на 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, за исключением добавленного подраздела о новых управляющих последовательностях.

Блок 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. Интерпретируются управляющие последовательности в содержимом. Поскольку интерпретация выполняется последним шагом, разработчики могут писать управляющие последовательности, например \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, не прошёл бы.

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

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 (табуляция) алгоритмом не интерпретируются: обработка управляющих последовательностей происходит позже.

Алгоритм переформатирования отступов будет нормативно закреплён в 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 можно свободно использовать ", даже рядом с открывающим или закрывающим разделителем. Например:

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."
    """;

Однако в последовательностях из трёх символов " необходимо экранировать хотя бы один ", чтобы они не выглядели как закрывающий разделитель:

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

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

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

Во-первых, управляющая последовательность \<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 и связанных возможностей, которые могут быть добавлены в будущем, был единый язык escape-последовательностей.

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

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

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