Урок 3 из 11 старт около 25 мин

Текстовые блоки и современные методы строк

Многострочные литералы без экранирования, formatted(), strip(), lines(), repeat() и приёмы, которые заменяют громоздкую конкатенацию.

Проблема: многострочный текст в коде

До Java 15 любой фрагмент JSON, SQL или HTML внутри программы превращался в лестницу из кавычек, плюсов и \n. Такой код тяжело читать и легко сломать, забыв пробел на стыке строк. Текстовые блоки решают именно эту задачу: строковый литерал может занимать несколько строк и не требует экранирования кавычек внутри.

// Было
String json = "{\n" +
        "  \"name\": \"Фортуна\",\n" +
        "  \"city\": \"Москва\"\n" +
        "}";

// Стало
String json = """
        {
          "name": "Фортуна",
          "city": "Москва"
        }
        """;

Блок начинается с трёх кавычек и обязательного перевода строки, а заканчивается тремя кавычками. Результат — обычная строка String: у неё нет специального типа, и её можно передавать куда угодно.

Как компилятор убирает отступы

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

String a = """
        первая
          вторая
        """;        // "первая\n  вторая\n"

String b = """
        первая
          вторая
    """;            // "    первая\n      вторая\n"

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

Две новые escape-последовательности

  • \s — один пробел, который переживёт удаление хвостовых пробелов;
  • \ в самом конце строки — соединить её со следующей без перевода строки. Так удобно переносить длинное предложение в коде, не добавляя \n в результат.
String message = """
        Это длинное предложение, которое в коде удобнее \
        разбить на две строки, но в результате оно останется одним.
        """;

Подстановка значений: formatted и String.format

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

String letter = """
        Здравствуйте, %s!
        Ваш урок номер %d начинается в %s.
        """.formatted("Анна", 3, "19:00");
System.out.print(letter);

Плейсхолдер %s принимает любое значение, %d — целое, %.2f — число с двумя знаками после запятой. Для дат лучше использовать форматирование из java.time, о котором речь в девятом уроке, а не подстановку через %s.

Методы строк, которых не было в старых учебниках

МетодЧто делаетПример
strip()Убирает пробелы и любые Unicode-пробельные символы по краям; trim() знал только про ASCII." текст ".strip()"текст"
isBlank()Истина, если строка пуста или состоит из пробелов." ".isBlank()true
lines()Поток строк, разбитый по переводам строки любой платформы.text.lines().count()
repeat(n)Повторяет строку n раз."-".repeat(20)
indent(n)Сдвигает каждую строку на n пробелов; отрицательное n убирает отступ.code.indent(4)
chars()Поток кодов символов для подсчётов и фильтрации.s.chars().filter(Character::isDigit).count()
String.joinСклеивает элементы через разделитель.String.join(", ", list)

Пример: отчёт в консоль

Соберём таблицу без сторонних библиотек. Здесь пригодятся и текстовый блок, и repeat, и форматирование ширины колонок через %-12s:

record Row(String lesson, int minutes) {}

var rows = java.util.List.of(new Row("JShell", 25), new Row("var", 20), new Row("Строки", 25));
var header = """
        %-12s %7s
        %s
        """.formatted("Урок", "Минуты", "-".repeat(20));
var body = new StringBuilder(header);
for (var row : rows) {
    body.append("%-12s %7d%n".formatted(row.lesson(), row.minutes()));
}
System.out.print(body);
Урок          Минуты
--------------------
JShell            25
var               20
Строки            25

Спецификатор %n вставляет перевод строки, правильный для текущей операционной системы. Записи record из примера подробно разобраны в следующем уроке.

Частая ошибка

Склеивать строки плюсом в цикле. Каждая конкатенация создаёт новый объект, и на тысячах итераций это заметно. Для накопления текста используйте StringBuilder или Collectors.joining, а плюс оставьте для двух-трёх слагаемых.

Попробуйте сами

  1. Запишите текстовым блоком шаблон письма с двумя подстановками: имя и дата. Выведите его для двух разных получателей через formatted.
  2. Возьмите многострочную строку и посчитайте через lines(), сколько в ней непустых строк. Подсказка: filter(line -> !line.isBlank()).
  3. Поэкспериментируйте с положением закрывающих кавычек текстового блока и объясните, как изменился отступ результата. Проверьте вывод через System.out.print, а не println, чтобы увидеть, есть ли перевод строки в конце.