Как обрабатываются намерения в текстовом блоке (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()) подсчитываются начальные пробелы. Начальные пробелы в последней строке также считаются, даже если они пустые. Минимальное значение - наименьшее из этих значений.

Для каждой непустой строки удаляются минимальные начальные пробелы и любые конечные пробельные символы. Пустые строки заменяются пустой строкой.

Для обоих Strings минимальный отступ равен 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

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

  1. Знаки конца строки нормализованы до символа ASCII LF (...)

  2. Случайные пробелы удаляются, как если бы при выполненииString::stripIndent о персонажах в содержании.

  3. 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 пробелов)

Другие вопросы по тегам