Как обрабатываются намерения в текстовом блоке (Java 13)
Я только что попробовал новую функцию текстового блока в Java 13 и столкнулся с небольшой проблемой.
Я прочитал эту статью из Jaxcenter.
Закрывающие тройные кавычки влияют на формат.
String query = """
select firstName,
lastName,
email
from User
where id= ?
""";
System.out.println("SQL or JPL like query string :\n" + query);
Этот формат выше работает хорошо. Для выравнивания с закрывающим разделителем (""") многострочная строка оставляла пробелы перед каждой строкой.
Но когда я попытался сравнить следующие две строки текстового блока, они имели тот же формат в консоли вывода, но они не равны, даже после stripIntent
.
String hello = """
Hello,
Java 13
""";
String hello2 = """
Hello,
Java 13
""";
System.out.println("Hello1:\n" + hello);
System.out.println("Hello2:\n" + hello);
System.out.println("hello is equals hello2:" + hello.equals(hello2));
System.out.println("hello is equals hello2 after stripIndent():" + hello.stripIndent().equals(hello2.stripIndent()));
Консоль вывода похожа на:
hello is equals hello2:false
hello is equals hello2 after stripIndent():false
Я не уверен, где не так, или это цель дизайна текстового блока?
Обновление: просто напечатайте hello2 stripIntent,
System.out.println("hello2 after stripIntent():\n" + hello2.stripIndent());
Пробелы перед каждой строкой НЕ удаляютсяstripIntent
как и ожидалось.
Обновлено: после прочтения соответствующего java-документа, я думаю, что после компиляции текстового блока он должен был удалить левые намерения строк в блоке. Какова цельstripIntent
для текстового блока? Я знаю, что это легко понять, если использовать его на обычной струне.
Полный код здесь.
3 ответа
Есть понятие случайного белого пространства.
JEP 355: Текстовые блоки (предварительная версия)
Обработка во время компиляции
Текстовый блок - это постоянное выражение типа String, как и строковый литерал. Однако, в отличие от строкового литерала, содержимое текстового блока обрабатывается компилятором Java в три отдельных этапа:
Знаки конца строки в контенте переводятся в LF (\u000A). Цель этого перевода - следовать принципу наименьшего удивления при перемещении исходного кода Java между платформами.
Случайные пробелы вокруг содержимого, введенные для соответствия отступам исходного кода Java, удаляются.
Интерпретируются escape-последовательности в содержимом. Выполнение интерпретации в качестве последнего шага означает, что разработчики могут писать escape-последовательности, такие как \n, без их изменения или удаления на более ранних этапах.
...
Случайное пустое пространство
Вот пример HTML с использованием точек для визуализации пробелов, которые разработчик добавил для отступов:
String html = """ ..............<html> .............. <body> .............. <p>Hello, world</p> .............. </body> ..............</html> ..............""";
Поскольку открывающий разделитель обычно располагается в той же строке, что и оператор или выражение, которое потребляет текстовый блок, нет никакого реального значения в том, что 14 визуализированных пробелов начинают каждую строку. Включение этих пробелов в контент будет означать, что текстовый блок обозначает строку, отличную от той, которая обозначена конкатенированными строковыми литералами. Это повредит миграции и будет постоянным источником неожиданностей: весьма вероятно, что разработчик не хочет, чтобы эти пробелы в строке. Кроме того, закрывающий ограничитель обычно располагается так, чтобы соответствовать содержимому, что дополнительно предполагает, что 14 визуализированных пробелов не имеют значения.
...
Соответственно,подходящая интерпретация содержания текстового блока заключается в том, чтобы отличать случайные пробелы в начале и конце каждой строки от основных пробелов. Компилятор Java обрабатывает содержимое, удаляя случайные пробелы, чтобы получить то, что задумал разработчик.
Ваше предположение, что
Hello,
Java 13
<empty line>
равно
....Hello,
....Java 13
<empty line>
неточно, поскольку это важные пробелы, и они не будут удалены ни компилятором, ни String#stripIndent
.
Чтобы прояснить ситуацию, давайте продолжим представлять случайное белое пространство в виде точки.
String hello = """
....Hello,
....Java 13
....""";
String hello2 = """
Hello,
Java 13
""";
Напечатаем.
Hello,
Java 13
<empty line>
Hello,
Java 13
<empty line>
Давай позвоним String#stripIndent
на обоих и распечатайте результаты.
Hello,
Java 13
<empty line>
Hello,
Java 13
<empty line>
Чтобы понять, почему ничего не изменилось, нужно заглянуть в документацию.
String#stripIndent
Возвращает строку, значением которой является эта строка, с удаленными случайными пробелами в начале и в конце каждой строки.
Затем минимальный отступ (min) определяется следующим образом. Для каждой непустой строки (как определено
isBlank()
) подсчитываются начальные пробелы. Начальные пробелы в последней строке также считаются, даже если они пустые. Минимальное значение - наименьшее из этих значений.Для каждой непустой строки удаляются минимальные начальные пробелы и любые конечные пробельные символы. Пустые строки заменяются пустой строкой.
Для обоих String
s минимальный отступ равен 0
.
Hello, // 0
Java 13 // 0 min(0, 0, 0) = 0
<empty line> // 0
Hello, // 4
Java 13 // 4 min(4, 4, 0) = 0
<empty line> // 0
String#stripIndent
предоставляет разработчикам доступ к Java-версии алгоритма повторного отступа, используемого компилятором.
JEP 355
Алгоритм повторного отступа будет нормативным в Спецификации языка Java. Разработчики будут иметь к нему доступ через
String::stripIndent
, новый метод экземпляра.Спецификация для JEP 355
Строка, представленная текстовым блоком, не является буквальной последовательностью символов в содержимом. Вместо этого строка, представленная текстовым блоком, является результатом применения следующих преобразований к содержимому в следующем порядке:
Знаки конца строки нормализованы до символа ASCII LF (...)
Случайные пробелы удаляются, как если бы при выполнении
String::stripIndent
о персонажах в содержании.Escape-последовательности интерпретируются как строковый литерал.
TL; DR. Строки в вашем примере не равны, и правильно, что Java сообщает вам, что они не равны.
Прочтите описание String.stripIndent
метод. Вот парафраз из сообщения jaxenter.com:
Метод stripIndent удаляет пробелы перед многострочными строками, которые есть у всех строк, т.е. перемещает весь текст влево без изменения форматирования.
Обратите внимание на слова "что все линии имеют общее".
Теперь примените "что все строки имеют общего" к следующей литеральной строке:
String hello2 = """
Hello,
First, notice that the final line of this example has zero spaces.
Next, notice that all other lines of this example have non-zero spaces.
"""; // <--- This is a line in the text block.
Ключевой вывод - "0!= 3".
Тестирование с jshell
:
String hello = """
Hello,
Java 13
""";
hello.replace(" ", ".");
приводит к
"Hello\nJava13\n"
примечание: вообще без пробелов
String hello2 = """
Hello,
Java 13
""";
hello2.replace(" ", ".");
приводит к
"....Hello\n....Java13\n"
Обратите внимание, что оба результата НЕ содержат пробелов в последней строке после последнего \n
, так stripIndent()
не удаляет пробелы
stripIndent()
делает то же самое, что и компилятор с текстовыми блоками. пример
String hello3 = ""
+ " Hello\n"
+ " Java13\n"
+ " ";
hello3.stripIndent().replace(" ", ".");
приводит к
"..Hello\n..Java13\n"
то есть два пробела удалены из всех 3 строк; два пробела, так как последняя строка имеет 2 пробела (в других строках их больше, поэтому из всех строк можно удалить не более 2 пробелов)