Форматирование строк в Java
Форматируйте строки в Java с помощью String.format и printf, используя спецификаторы %s, %d, %f, %n.
Конкатенация (+) подходит для коротких и простых строк. Но как только нужно зафиксировать число с определённым количеством знаков после запятой, выровнять столбцы отступами, вставить дату в стандартном формате или иначе форматировать значения вместо простой вставки — приходит время воспользоваться API в стиле printf, который Java позаимствовала из C.
Существует три тесно связанных точки входа, и все они встречаются в реальном коде:
String.format(fmt, args...)— возвращает отформатированнуюString."...".formatted(args...)— экземплярная форма, добавленная в Java 15. Результат идентичен, удобнее для цепочек вызовов.System.out.printf(fmt, args...)— печатает напрямую вPrintStream(или любойFormatter).
Все три используют один и тот же синтаксис спецификаторов формата. Изучите его один раз.
Спецификаторы формата
Спецификатор имеет вид %[flags][width][.precision]conversion. Буква преобразования — единственный обязательный элемент. Остальное настраивает ширину, выравнивание, заполнение и точность.
String s = String.format("%-10s | %5d | %8.2f", "apples", 42, 3.14159);
// "apples | 42 | 3.14"Наиболее полезные преобразования:
| Преобразование | Аргумент | Значение |
|---|---|---|
%s | любой | toString() значения |
%d | целое | десятичное целое число |
%f | с плавающей точкой | число с фиксированной точкой |
%e | с плавающей точкой | научная запись |
%g | с плавающей точкой | кратчайшее из %e и %f |
%x, %o | целое | шестнадцатеричное / восьмеричное |
%c | символ | одиночный символ |
%b | любой | true/false (null → "false") |
%n | нет | разделитель строк для текущей платформы |
%% | нет | литеральный символ % |
%t... | дата/время | целое семейство — %tF, %tT и др. |
Ширина дополняет вывод минимум до N символов; точность означает «цифры после запятой» для чисел с плавающей точкой и «максимальное число символов» для строк.
Флаги: выравнивание, знак, нули, группировка
Несколько флагов размещаются между % и шириной:
-— выравнивание по левому краю в пределах ширины. По умолчанию — по правому краю.0— дополнять нулями до заданной ширины (только для числовых преобразований).+— всегда показывать знак у чисел (+42,-7).(пробел) — показывать ведущий пробел для положительных чисел, как+, но с пробелом.,— группировать цифры с помощью разделителя тысяч, принятого в локали.(— отрицательные числа в скобках, бухгалтерский стиль.
String.format("%08d", 42); // "00000042"
String.format("%,d", 1234567); // "1,234,567"
String.format("%+.2f", 3.14159); // "+3.14"
String.format("%-10s|", "hi"); // "hi |"Ширина и точность
Ширина — это минимальная ширина поля: если отформатированное значение шире, ничего не усекается.
Точность означает разное для разных преобразований:
%.3f— три цифры после десятичной точки.%.10s— усечь строку до не более 10 символов.%.4e— четыре цифры точности мантиссы.
Комбинирование ширины и точности часто используется, когда нужно одновременно выровнять столбцы и округлить числа:
String.format("%10.4f", Math.PI); // " 3.1416"
String.format("%-10.4s", "abcdef"); // "abcd "%n против \n
%n выводит разделитель строк, соответствующий платформе: "\n" на Unix, "\r\n" на Windows. \n — всегда ровно один байт. Для файлов и протокольного вывода, где конец строки важен точно, лучше использовать \n и выбирать его осознанно. Для консольного вывода, который должен корректно отображаться на любой ОС, где запущена JVM, %n — более безопасный выбор.
Индексы аргументов: повторное использование и переупорядочение
Спецификатор вида %N$... ссылается на N-й аргумент (нумерация с 1). Полезен, когда одно значение встречается в шаблоне более одного раза или когда естественный порядок чтения отличается от порядка аргументов:
String.format("%1$s, %1$s, %1$s!", "go"); // "go, go, go!"
String.format("%2$s before %1$s", "lunch", "tea"); // "tea before lunch"Это правильный инструмент при локализации шаблонов — в разных языках существительные стоят на разных позициях, и переводчик может переставлять плейсхолдеры, не трогая место вызова.
Локаль важна для чисел и дат
Форматирование чисел учитывает локаль JVM по умолчанию, если вы не переопределите её явно. В en-US вы получите 3.14; в de-DE — 3,14; во fr-FR тысячи разделяются неразрывным пробелом. Для вывода, предназначенного человеку, это обычно то, что нужно. Для форматов данных — JSON, CSV, лог-файлов, любого машиночитаемого формата — это катастрофа, ожидающая развёртывания во Франкфурте.
Всегда явно передавайте локаль для машиночитаемого вывода:
String json = String.format(Locale.ROOT, "{\"price\": %.2f}", 19.95);
// "{\"price\": 19.95}" — always, regardless of JVM localeLocale.ROOT означает «без форматирования, специфичного для локали» — точка как десятичный разделитель, без группировки. Locale.US — другой распространённый вариант для той же цели. Опасность — не передавать локаль и делать предположения.
Форматирование даты и времени
%t — мета-преобразование: следующая за ним буква выбирает поле. Один и тот же аргумент типа Date, Calendar, Long (миллисекунды) или java.time.temporal.TemporalAccessor можно форматировать множеством способов:
LocalDateTime now = LocalDateTime.of(2026, 5, 29, 14, 30, 15);
String.format("%tF", now); // "2026-05-29" — ISO date
String.format("%tT", now); // "14:30:15" — 24-hour time
String.format("%tA", now); // "Friday" — locale-dependent
String.format("%1$tF %1$tT", now); // "2026-05-29 14:30:15"Для всего, что выходит за рамки быстрого форматирования, API java.time.format.DateTimeFormatter более гибкий и учитывает локаль — но %tF и подобные им по-прежнему удобны в строках лога.
Типичные ошибки
- Неверное преобразование для типа.
String.format("%d", 3.14)выбрасываетIllegalFormatConversionExceptionво время выполнения —%dожидает целое,%fожидает число с плавающей точкой. Компилятор это не проверяет. - Отсутствующие аргументы. Пропуск аргумента плейсхолдера выбрасывает
MissingFormatArgumentException. - Зависящий от локали десятичный разделитель в машинном выводе. Рассмотрено выше.
%sдляnull. Выдаёт"null". Допустимо для логов, неудобно в пользовательском интерфейсе.- Использование
+для отформатированных чисел."Price: " + 19.95даёт"Price: 19.95", но"Price: " + 0.1 + 0.2даёт"Price: 0.10.2", а не"Price: 0.3"— это конкатенация, а не сложение.
Практический пример
Небольшой форматировщик сводки заказа, демонстрирующий ширину, точность, выравнивание, группировку, локаль и преобразование дат через %t. Результат — аккуратный двухколоночный отчёт, который иначе пришлось бы строить вручную с помощью вспомогательных функций padLeft.
Два момента, заслуживающих внимания: %<tT в строке «Placed» повторно использует предыдущий аргумент (< — флаг обратной ссылки), избегая лишнего второго placedAt. А строка JSON использует Locale.ROOT — тот же код на немецкой JVM по-прежнему выдаёт 1339.43, а не 1339,43, что именно и ожидает JSON-парсер.
Что дальше
Построение строк — это половина задачи. Вторая половина — их сравнение: равенство, упорядочение, приведение регистра, разница между == и equals — и это частый источник неочевидных ошибок. Переходите к сравнению строк в Java.