Форматирование строк в Java: String.format(), printf() и Formatter
Строка String.format("%.2f", 0.5) на компьютере с русскими региональными настройками вернёт не "0.50", а "0,50" — и следующий за ней Double.parseDouble(...) упадёт с NumberFormatException: For input string: "0,50". Чтобы такие сюрпризы не случались, нужно понимать, как устроено форматирование строк в Java.
Форматирование строк в Java — это построение строки по шаблону, в котором спецификаторы формата (%d, %s, %.2f и другие) заменяются значениями аргументов. Форматированный вывод выполняют методы String.format(), String.formatted() (Java 15+), System.out.printf() и класс java.util.Formatter, который лежит в основе их всех. Форматировать можно числа, строки, символы, даты и время.
String.format(), printf() и formatted(): какой метод выбрать
Если результат нужен как строка — используйте String.format() или "шаблон".formatted(); если его нужно сразу напечатать — System.out.printf(); если строку нужно собрать по частям или записать в файл — класс Formatter. Все эти методы понимают один и тот же синтаксис шаблона.
| Метод | Что возвращает | С какой версии | Когда использовать |
|---|---|---|---|
String.format(шаблон, args...) | Новую строку | Java 5 | Нужно получить строку (аналог sprintf из C) |
"шаблон".formatted(args...) | Новую строку | Java 15 | То же, но короче; удобно с текстовыми блоками |
System.out.printf(...), System.out.format(...) | Сам PrintStream, печатает в консоль | Java 5 | Форматированный вывод в консоль |
PrintWriter.printf(...) | Сам PrintWriter, пишет в поток | Java 5 | Запись в файл или сетевой поток |
new Formatter(...).format(...) | Сам Formatter, накапливает результат | Java 5 | Сборка текста из нескольких вызовов, запись в Appendable или файл |
System.out — это статическая переменная типа PrintStream, поэтому у неё есть методы printf() и format(). Это два имени одного и того же метода: printf() добавлен для привычности программистам на C. Внутри все перечисленные методы используют Formatter.
Здесь и далее вывод в комментариях приведён для локали Locale.US: в русской локали вместо десятичной точки будет запятая (подробнее — в разделе Locale).
public class PrintfExample {
public static void main(String[] args) {
System.out.printf("Строка с форматированием: %.2f%n", 1000.0 / 3.0);
System.out.format("%s, в следующем году вам будет %d%n", "Джон", 23);
String str = String.format("Цена: %.2f руб.", 99.5);
System.out.println(str);
String greeting = "Привет, %s! У вас %d новых сообщений.".formatted("Анна", 5);
System.out.println(greeting);
}
}
// Вывод:
// Строка с форматированием: 333.33
// Джон, в следующем году вам будет 23
// Цена: 99.50 руб.
// Привет, Анна! У вас 5 новых сообщений. Метод formatted() особенно удобен с текстовыми блоками (Java 15+): шаблон пишется как есть, без конкатенации и \n.
String json = """
{"name": "%s", "age": %d}
""".formatted("Анна", 30);
System.out.print(json);
// Вывод: {"name": "Анна", "age": 30} Синтаксис спецификатора формата
Спецификатор формата — это фрагмент шаблона, который начинается со знака % и описывает, как вывести очередной аргумент. Общий вид: %[argument_index$][flags][width][.precision]conversion; обязательны только % и символ преобразования (conversion), остальные части необязательны.
%[argument_index$][flags][width][.precision]conversion | Часть | Что задаёт | Пример |
|---|---|---|
argument_index$ | Номер аргумента (с 1), заканчивается знаком $ | %2$s — второй аргумент |
flags | Выравнивание, знак, ведущие нули, разделители групп | %-10s, %,d, %+d |
width | Минимальная ширина поля в символах | %8d |
.precision | Знаков после точки для чисел; максимальная длина для строк | %.2f, %.3s |
conversion | Тип преобразования (обязателен) | d, s, f, x |
Индекс аргумента позволяет использовать одно значение несколько раз или менять порядок аргументов. Запись %<s означает «тот же аргумент, что в предыдущем спецификаторе»:
public class ArgumentIndexExample {
public static void main(String[] args) {
System.out.printf("Hello %1$s!%n%1$s, how are you?%n"
+ "Welcome to the site %2$s%n",
"John", "www.site.com");
System.out.printf("%d в шестнадцатеричной системе: %<x%n", 255);
}
}
// Вывод:
// Hello John!
// John, how are you?
// Welcome to the site www.site.com
// 255 в шестнадцатеричной системе: ff Спецификаторы формата: таблица
Символ преобразования определяет, как интерпретируется аргумент: %d — целое число, %f — дробное, %s — строка, %c — символ, %b — логическое значение. Тип аргумента должен подходить к спецификатору, иначе будет исключение IllegalFormatConversionException.
| Спецификатор | Что выводит | Пример | Результат |
|---|---|---|---|
%d | Десятичное целое (int, long, BigInteger…) | format("%d", 42) | 42 |
%s / %S | Строковое представление (toString()) | format("%S", "java") | JAVA |
%f | Дробное число в десятичной записи, по умолчанию 6 знаков после точки | format("%f", 3.14159) | 3.141590 |
%e / %E | Дробное число в экспоненциальной (научной) записи | format("%e", 12345.678) | 1.234568e+04 |
%g / %G | Дробное число: научная запись, если после округления значение меньше 10-4 или не меньше 10точность, иначе десятичная. Точность = общее число значащих цифр (по умолчанию 6) | format("%g", 12345.678)format("%g", 0.00001234) | 12345.71.23400e-05 |
%a / %A | Дробное число в шестнадцатеричной записи | format("%a", 1.0) | 0x1.0p0 |
%x / %X | Целое в шестнадцатеричной системе | format("%X", 255) | FF |
%o | Целое в восьмеричной системе | format("%o", 8) | 10 |
%c / %C | Символ (char или код символа int) | format("%c", 65) | A |
%b / %B | Логическое значение: для null — false, для Boolean — его значение, для любого другого объекта — true | format("%b", null) | false |
%h / %H | Хеш-код аргумента в шестнадцатеричном виде | format("%h", "hi") | d01 |
%t / %T | Дата и время (с дополнительным суффиксом: %tY, %tF…) | format("%tY", LocalDate.of(2026, 10, 16)) | 2026 |
%n | Перевод строки, принятый в операционной системе | format("a%nb") | a и b на разных строках |
%% | Сам знак % | format("%d%%", 50) | 50% |
Заглавные варианты (%S, %X, %E, %G, %A, %B, %H, %C, %T) работают так же, как строчные, но переводят результат в верхний регистр: %x даёт ff, %X — FF; %b даёт true, %B — TRUE. Для %d, %f, %o заглавных вариантов нет.
Что выведет этот код?
System.out.printf("%b %b %b%n", "false", null, 0); Ответ: true false true. Строка "false" — не Boolean, а обычный ненулевой объект, поэтому для %b она превращается в true. Число 0 тоже даёт true: в Java, в отличие от C, ноль не означает «ложь». И только null превращается в false.
Ширина и точность
Ширина (%10s) задаёт минимальное число символов в поле: короткое значение дополняется пробелами слева, а с флагом - — справа. Точность (%.2f) для дробных чисел задаёт число знаков после точки с округлением по правилу HALF_UP, а для строк (%.3s) — максимальную длину, лишнее отбрасывается.
public class WidthPrecisionExample {
public static void main(String[] args) {
double x = 1000.0 / 3.0;
System.out.println("Без форматирования: " + x);
System.out.printf("[%.2f]%n", x);
System.out.printf("[%8.2f]%n", x);
System.out.printf("[%-8.2f]%n", x);
System.out.printf("[%10s][%-10s]%n", "Java", "Java");
System.out.printf("[%.3s]%n", "Formatter");
System.out.printf("[%3s]%n", "Formatter");
}
}
// Вывод:
// Без форматирования: 333.3333333333333
// [333.33]
// [ 333.33]
// [333.33 ]
// [ Java][Java ]
// [For]
// [Formatter] Последняя строка показывает неочевидный момент: ширина — это минимум, а не максимум. Если значение длиннее, оно выводится целиком, и колонки «разъезжаются». Чтобы обрезать строку, нужна точность: %-10.10s выведет ровно 10 символов.
Ширина и выравнивание пригодятся для вывода таблиц в консоль:
public class TableExample {
public static void main(String[] args) {
System.out.printf("%-10s%8s%n", "Товар", "Цена");
System.out.printf("%-10s%8.2f%n", "Хлеб", 45.5);
System.out.printf("%-10s%8.2f%n", "Молоко", 89.9);
}
}
// Вывод:
// Товар Цена
// Хлеб 45.50
// Молоко 89.90 Флаги формата
Флаги ставятся сразу после % (или после индекса аргумента) и управляют выравниванием, знаком числа, ведущими нулями и разделителями разрядов. Несколько флагов можно комбинировать: %,(.2f выведет отрицательное число в скобках и с разделителями групп.
| Флаг | Что делает | Пример | Результат |
|---|---|---|---|
- | Выравнивание по левому краю (требует ширину) | format("[%-5d]", 42) | [42 ] |
0 | Дополнение нулями вместо пробелов (только числа, требует ширину) | format("%05d", 42) | 00042 |
+ | Всегда выводить знак числа | format("%+d", 42) | +42 |
| пробел | Перед положительным числом ставится пробел (для выравнивания с отрицательными) | format("[% d]", 42) | [ 42] |
, | Разделители групп разрядов по правилам локали | format("%,d", 1234567) | 1,234,567 |
( | Отрицательные числа заключаются в скобки | format("%(d", -42) | (42) |
# | Альтернативная форма: префикс 0x для %x, 0 для %o | format("%#x", 255) | 0xff |
public class FlagsExample {
public static void main(String[] args) {
System.out.printf("%,.2f%n", 10000.0 / 3.0);
System.out.printf("%,(.2f%n", -10000.0 / 3.0);
System.out.printf("%09.2f%n", 10000.0 / 3.0);
}
}
// Вывод:
// 3,333.33
// (3,333.33)
// 003333.33 Несовместимые флаги вызывают исключение: + вместе с пробелом или - вместе с 0 дают IllegalFormatFlagsException, а - или 0 без ширины — MissingFormatWidthException.
Класс Formatter
Класс java.util.Formatter — базовый механизм форматированного вывода в Java: String.format() и printf() создают его внутри себя. Напрямую Formatter используют, когда текст нужно собрать за несколько вызовов format() или писать результат сразу в StringBuilder, файл или поток.
Метод format() класса Formatter преобразует аргументы по шаблону и добавляет результат к накопленному тексту; получить его можно через toString():
import java.util.Formatter;
public class FormatterExample {
public static void main(String[] args) {
Formatter formatter = new Formatter();
formatter.format("This %s is about %n%S %c", "book", "java", '8');
System.out.println(formatter);
}
}
// Вывод:
// This book is about
// JAVA 8 Конструктор Formatter(Appendable, Locale) позволяет писать прямо в StringBuilder и сразу задать локаль. Formatter реализует Closeable, поэтому при записи в файл его закрывают в try-with-resources:
import java.util.Formatter;
import java.util.Locale;
public class FormatterBuilderExample {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder();
Formatter formatter = new Formatter(sb, Locale.US);
formatter.format("%-8s%6.2f%n", "Чай", 3.5);
formatter.format("%-8s%6.2f%n", "Кофе", 12.0);
System.out.print(sb);
}
}
// Вывод:
// Чай 3.50
// Кофе 12.00 Locale: точка или запятая
Форматирование чисел и дат зависит от локали: без явного Locale методы String.format() и printf() берут локаль системы (Locale.getDefault(Locale.Category.FORMAT)). В русской локали десятичный разделитель — запятая, а разделитель групп — неразрывный пробел, поэтому одна и та же программа выводит разные строки на разных компьютерах.
import java.util.Locale;
public class LocaleExample {
public static void main(String[] args) {
double price = 1234.567;
Locale ru = Locale.forLanguageTag("ru-RU");
System.out.println(String.format(Locale.US, "%,.2f", price));
System.out.println(String.format(ru, "%,.2f", price));
System.out.println(String.format(Locale.ROOT, "%.2f", price));
}
}
// Вывод:
// 1,234.57
// 1 234,57
// 1234.57 Методы format() и printf() имеют перегрузку с первым параметром Locale. У formatted() такой перегрузки нет — он всегда использует локаль по умолчанию. Конструктор new Locale("ru", "RU") объявлен устаревшим в Java 19; вместо него используйте Locale.forLanguageTag("ru-RU") или Locale.of("ru", "RU") (Java 19+).
Совет
Для данных, которые будет читать программа (CSV, JSON, SQL, логи для парсинга), передавайте Locale.ROOT или Locale.US явно. Локаль по умолчанию оставляйте только для текста, который читает человек.
Форматирование даты и времени
Спецификатор %t (или %T для верхнего регистра) с суффиксом выводит часть даты или времени: %tY — год, %tm — месяц, %td — день, %tH:%tM — часы и минуты, %tF — дату в формате ISO. Аргументом может быть LocalDate, LocalDateTime, Date, Calendar или long.
import java.time.LocalDate;
public class DateFormatExample {
public static void main(String[] args) {
LocalDate date = LocalDate.of(2026, 10, 16);
System.out.println(String.format("%tF", date));
System.out.println(String.format("%1$td.%1$tm.%1$tY", date));
}
}
// Вывод:
// 2026-10-16
// 16.10.2026 Обратите внимание на %1$: без индекса каждый спецификатор %t ждёт свой аргумент, и шаблон "%td.%tm.%tY" с одной датой бросит MissingFormatArgumentException. Для сложных форматов дат в современном коде удобнее java.time.format.DateTimeFormatter.
На чём чаще всего ошибаются
Ошибки в шаблоне не ловятся компилятором: они проявляются только при выполнении в виде непроверяемых исключений — наследников IllegalFormatException (а он наследует IllegalArgumentException). Самые частые случаи и их исправление:
| Код | Что произойдёт | Как исправить |
|---|---|---|
format("%d", 3.5) | IllegalFormatConversionException: d != java.lang.Double | %f для дробных или приведение (int) |
format("%s и %s", "A") | MissingFormatArgumentException: Format specifier '%s' | Передать все аргументы или использовать %1$s |
format("%-05d", 7) | IllegalFormatFlagsException: Flags = '-0' | Выбрать один флаг: %-5d или %05d |
format("%i", 7) (привычка из C) | UnknownFormatConversionException: Conversion = 'i' | В Java целые выводятся только через %d |
format("Скидка 50%") | UnknownFormatConversionException | Экранировать знак процента: 50%% |
format("%s", "a", "b") | Исключения нет, вывод a — лишний аргумент молча игнорируется | Сверять число спецификаторов и аргументов (IDE подсвечивает) |
format("%d", (Integer) null) | Исключения нет, вывод null | Проверять значение на null заранее |
%n или \n?
В шаблонах используйте %n: он подставляет перевод строки текущей ОС (\r\n в Windows, \n в Linux и macOS). \n всегда даёт один символ LF — это правильно, только если формат файла или протокола требует именно его.
DecimalFormat и MessageFormat
Кроме String.format(), в пакете java.text есть два класса форматирования: DecimalFormat задаёт формат числа шаблоном вида #,##0.00, а MessageFormat подставляет аргументы по номерам в фигурных скобках {0}, {1} — так устроены файлы локализации интерфейса.
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.MessageFormat;
import java.util.Locale;
public class TextFormatExample {
public static void main(String[] args) {
DecimalFormat df = new DecimalFormat("#,##0.00",
DecimalFormatSymbols.getInstance(Locale.US));
System.out.println(df.format(1234567.891));
System.out.println(MessageFormat.format("Файл {0} содержит {1} строк", "data.txt", 1500));
System.out.println(MessageFormat.format("Файл {0} содержит {1,number,#} строк", "data.txt", 1500));
}
}
// Вывод:
// 1,234,567.89
// Файл data.txt содержит 1,500 строк
// Файл data.txt содержит 1500 строк Нюанс MessageFormat: числа в {1} форматируются по локали с разделителями групп, поэтому 1500 превращается в 1,500. Формат {1,number,#} выводит число без разделителей. Ещё одна особенность: одинарная кавычка в шаблоне MessageFormat служит экранирующим символом, и чтобы вывести апостроф, её нужно удвоить ('').
Часто задаваемые вопросы
Есть ли в Java аналог функции sprintf из C?
Да, это String.format() (или "шаблон".formatted() в Java 15+): он возвращает отформатированную строку, а System.out.printf() соответствует printf из C. Синтаксис похож, но есть отличия: нет %i и %u, для перевода строки есть %n, а %b работает с любым объектом. Подробнее — в разделе На чём чаще всего ошибаются.
Чем printf() отличается от println()?
println() печатает одно значение и всегда добавляет перевод строки. printf() принимает шаблон и любое число аргументов, а перевод строки сам не добавляет — его нужно указать в шаблоне через %n.
Что быстрее: String.format() или конкатенация через +?
Конкатенация заметно быстрее: String.format() при каждом вызове разбирает шаблон и создаёт объект Formatter. В циклах и горячем коде собирайте строки через + или StringBuilder, а String.format() используйте там, где важны читаемость и выравнивание.
Как оставить два знака после запятой без округления?
%.2f всегда округляет по правилу HALF_UP: String.format("%.2f", 1.999) даст 2.00. Чтобы отбросить лишние знаки, используйте BigDecimal: new BigDecimal("1.999").setScale(2, RoundingMode.DOWN) вернёт 1.99.
Practice
Reinforce this lesson with hands-on tasks, each with a level and a time estimate.
All module tasks →Video Explanation
Prefer video format? Watch this lesson with examples and explanations.
Comments