Форматирование и интернационализация
Научимся красиво оформлять числа, строки, валюты и даты. Познакомимся с локалями и созданием программ, понятных людям всего мира
Введение — зачем нужно форматирование?
Представьте, что вы работаете кассиром в магазине. Клиент покупает товары на сумму 1234.5 рублей. Как вы напишете цену на чеке? Конечно же, так: 1 234,50 ₽, а не так: 1234.5. В первом случае человек сразу видит целую и дробную часть, понимает валюту, замечает разделитель тысяч. Это и есть форматирование — превращение «сырых» данных в удобочитаемый вид.
В программировании точно такая же задача. Когда Java выводит число 3.141592653589793, это технически корректно, но неудобно. Когда программа выводит дату в формате 1750819200000 (миллисекунды), пользователь не поймёт, что это значит. Форматирование превращает эти «сырые» значения в то, что человек сразу понимает.
Помимо форматирования, в этом уроке мы разберём интернационализацию (i18n) — процесс адаптации программы под разные языки и регионы. Знаете ли вы, что в России десятичный разделитель — это запятая, а в США — точка? Что формат даты 01.02.2025 в России означает 1 февраля, а в Америке — 2 января? Если ваша программа используется в разных странах, эти различия необходимо учитывать.
Java предоставляет мощный набор инструментов для форматирования и интернационализации. Сегодня мы подробно разберём каждый из них:
- System.out.printf — вывод отформатированной информации в консоль
- String.format — создание отформатированных строк без вывода
- DecimalFormat — гибкое форматирование чисел
- NumberFormat — форматирование чисел, валют и процентов по правилам локали
- Locale — настройка языка и региона
- MessageFormat — интернационализация сообщений
System.out.printf — форматированный вывод в консоль
Метод printf — это аббревиатура от «print formatted» (вывести отформатированное). Он заимствован из языка C, где используется уже более 40 лет. Принцип работы прост: вы даёте Java «шаблон» строки, в котором указываете места для подстановки значений, а затем передаёте сами значения.
Представьте это как бланк документа. У вас есть шаблон с пустыми строками: «ФИО: ____, дата рождения: ____». Вы заполняете пустые строки нужной информацией. Точно так же printf подставляет значения в места, обозначенные специальными символами (спецификаторами).
Простейший пример
Давайте начнём с самого базового:
public class PrintfDemo {
public static void main(String[] args) {
String name = "Алексан";
int age = 25;
double salary = 75000.5;
System.out.printf("Имя: %s%n", name);
System.out.printf("Возраст: %d лет%n", age);
System.out.printf("Зарплата: %.2f рублей%n", salary);
System.out.printf("Сотрудник %s (%d лет) получает %.2f руб.%n", name, age, salary);
}
}
Вывод программы:
Имя: Александр
Возраст: 25 лет
Зарплата: 75000.50 рублей
Сотрудник Александр (25 лет) получает 75000.50 руб.
Видите? Мы написали %s, %d, %.2f в строке-шаблоне, а Java подставила вместо них реальные значения. Это гораздо удобнее, чем склеивать строки через конкатенацию.
Аналогия: Представьте, что printf — это рецептурный бланк у врача. Врач пишет «Принимать по 2 таблетки 3 раза в день после еды». Вы подставляете своё имя, дозировку и название препарата. Шаблон один, а данные у каждого пациента разные.
Основные спецификаторы формата
Спецификатор формата — это комбинация символов, которая начинается с процента % и определяет, как именно значение будет отображаться. Вот основные из них:
| Спецификатор | Назначение | Пример значения |
|---|---|---|
%s |
Строка | "Привет" |
%d |
Целое число (decimal) | 42, -7, 1000 |
%f |
Число с плавающей запятой | 3.14, -0.5 |
%c |
Символ | 'A', 'я', '5' |
%b |
Логическое значение | true, false |
%n |
Перенос строки | \n или \r\n |
%% |
Символ процента literal | % |
%e |
Научная нотация | 1.23e+02 |
%o |
Восьмеричное число | 52 |
%x |
Шестнадцатеричное число | 2a, FF |
Давайте посмотрим на каждый спецификатор подробнее:
public class SpecifiersDemo {
public static void main(String[] args) {
String text = "Java";
int number = 255;
double pi = 3.14159;
char grade = 'A';
boolean isGood = true;
System.out.printf("%%s — строка: %s%n", text);
System.out.printf("%%d — целое число: %d%n", number);
System.out.printf("%%f — дробное число: %f%n", pi);
System.out.printf("%%e — научная нотация: %e%n", pi);
System.out.printf("%%c — символ: %c%n", grade);
System.out.printf("%%b — логическое: %b%n", isGood);
System.out.printf("%%o — восьмеричное: %o%n", number);
System.out.printf("%%x — шестнадцатеричное: %x%n", number);
System.out.printf("255%% = 255%%n");
}
}
Вывод:
%s — строка: Java
%d — целое число: 255
%f — дробное число: 3.141590
%e — научная нотация: 3.141590e+00
%c — символ: A
%b — логическое: true
%o — восьмеричное: 377
%x — шестнадцатеричное: ff
255% = 255%
Почему %n, а не \n? Символ \n добавляет именно символ переноса строки Linux. Но на Windows перенос строки — это \r\n (два символа). Спецификатор %n автоматически подставляет правильный перенос для текущей операционной системы. Всегда используйте %n в printf вместо \n — это сделает вашу программу кроссплатформенной.
Ширина поля (width)
Ширина поля определяет минимальное количество символов, которое будет отведено для значения. Если значение короче — оно дополнится пробелами. Это очень удобно для создания табличных отчётов.
Аналогия: представьте таблицу в Excel. Каждая колонка имеет определённую ширину. Данные центрируются или выравниваются по левому/правому краю внутри ячейки. Спецификатор ширины делает то же самое.
public class WidthDemo {
public static void main(String[] args) {
System.out.printf("1234567890%n");
System.out.printf("[%10s]%n", "Java");
System.out.printf("[%-10s]%n", "Java");
System.out.printf("[%20s]%n", "Привет мир");
System.out.printf("Целое число с шириной 10: [%10d]%n", 42);
System.out.printf("Целое число с шириной 10: [%-10d]%n", 42);
System.out.printf("Число с шириной 15: [%15.2f]%n", 1234.5);
System.out.printf("Число с шириной 15: [%-15.2f]%n", 1234.5);
}
}
Вывод:
1234567890
[ Java]
[Java ]
[ Привет мир]
Целое число с шириной 10: [ 42]
Целое число с шириной 10: [42 ]
Число с шириной 15: [ 1234.50]
Чисро с шириной 15: [1234.50 ]
Как видите:
%10s— строка выравнивается по правому краю и занимает минимум 10 символов%-10s— минус перед числом выравнивает по левому краю%10d— целое число с шириной 10, дополненное пробелами слева%-10d— целое число выровнено по левому краю
Точность (precision)
Точность определяет количество знаков после запятой для чисел с плавающей точкой. Для строк — максимальную длину вывода.
public class PrecisionDemo {
public static void main(String[] args) {
double price = 1234.56789;
System.out.printf("Без ограничений: %f%n", price);
System.out.printf("2 знака: %.2f%n", price);
System.out.printf("0 знаков: %.0f%n", price);
System.out.printf("6 знаков: %.6f%n", price);
String longText = "Очень длинная строка текста";
System.out.printf("Строка до 10 символов: %.10s%n", longText);
System.out.printf("Ширина 20, точность 3: [%20.3f]%n", price);
}
}
Вывод:
Без ограничений: 1234.567890
2 знака: 1234.57
0 знаков: 1235
6 знаков: 1234.567890
Строка до 10 символов: Очень длинн
Ширина 20, точность 3: [ 1234.568]
Обратите внимание: при точности .0 число 1234.56789 округлилось до 1235. Java выполняет математическое округление (а не просто отбрасывание дробной части).
Флаги выравнивания и другие флаги
Помимо ширины и точности, в спецификаторе можно использовать флаги:
-— выравнивание по левому краю (по умолчанию — по правому)+—强制显示 знак + для положительных чисел0— дополнение нулями вместо пробелов,— разделитель групп (тысячи, миллионы)(— отрицательные числа в скобках
public class FlagsDemo {
public static void main(String[] args) {
int positive = 42;
int negative = -42;
double big = 1234567.89;
System.out.printf("Плюс: %+d%n", positive);
System.out.printf("Плюс отрицательное: %+d%n", negative);
System.out.printf("Нулями: %010d%n", positive);
System.out.printf("С разделителем: %,.2f%n", big);
System.out.printf("С разделителем целое: %,d%n", 9876543);
System.out.printf("Отрицательное в скобках: %(.2f%n", -1234.56);
System.out.printf("Всё вместе: %+020,.2f%n", big);
}
}
Вывод:
Плюс: +42
Плюс отрицательное: -42
Нулями: 0000000042
С разделителем: 1,234,567.89
С разделителем целое: 9,876,543
Отрицательное в скобках: (1234.56)
Всё вместе: +0000001,234,567.89
Флаг , особенно важен — он делает большие числа читаемыми. Вместо 1234567.89 вы видите 1,234,567.89. Это стандартный формат, который используется в финансовых отчётах по всему миру.
Порядок аргументов
Вы можете指定овать, какой аргумент подставить в какое место, используя индексы:
public class IndexDemo {
public static void main(String[] args) {
System.out.printf("Первый: %1$s, Второй: %2$s, Снова первый: %1$s%n",
"Москва", "Питер");
System.out.printf("%1$d + %2$d = %3$d%n", 10, 20, 30);
System.out.printf("%2$d минус %1$d = %4$d%n", 10, 20, 30, 10);
System.out.printf("Поля: [%1$10s] [%2$-8s] [%1$20s]%n", "Java", "OK");
}
}
Вывод:
Первый: Москва, Второй: Питер, Снова первый: Москва
10 + 20 = 30
20 минус 10 = 10
Поля: [ Java] [OK ] [ Java]
Индексы начинаются с 1 (не с 0, как в массивах). Это удобно для переиспользования одного значения в разных местах шаблона, например при создании шапки отчёта.
Аргументы формата — что ещё можно подставить
Помимо примитивных типов, printf умеет работать с объектами. Любой объект, у которого переопределён метод toString(), может быть подставлен через %s:
import java.time.LocalDate;
public class ObjectsPrintf {
public static void main(String[] args) {
LocalDate today = LocalDate.now();
int[] numbers = {1, 2, 3};
System.out.printf("Сегодня: %s%n", today);
System.out.printf("Массив: %s%n", numbers);
System.out.printf("null: %s%n", (Object) null);
System.out.printf("Символ: %c%n", (char) 65);
}
}
Вывод:
Сегодня: 2026-06-25
Массив: [I@1b6d3586
null: null
Символ: A
Важно: Массивы выводятся как [I@1b6d3586 — это внутреннее представление объекта в Java. Если хотите красивый вывод массива, используйте Arrays.toString() или метод Arrays.deepToString().
Практический пример: таблица сотрудников
Давайте создадим красиво оформленную таблицу сотрудников:
public class EmployeeTable {
public static void main(String[] args) {
String[][] employees = {
{"Иванов И.И.", "Программист", "95000.50", "5"},
{"Петров П.П.", "Дизайнер", "78000.00", "3"},
{"Сидоров С.С.", "Менеджер", "85000.75", "7"},
{"Козлова А.В.", "Аналитик", "92000.25", "4"}
};
String header = "| %-22s | %-16s | %14s | %6s |%n";
String line = "+------------------------+------------------+----------------+--------+%n";
System.out.printf(line);
System.out.printf(header, "ФИО", "Должность", "Зарплата", "Стаж");
System.out.printf(line);
for (String[] emp : employees) {
System.out.printf("| %-22s | %-16s | %14s | %6s |%n",
emp[0], emp[1], emp[2], emp[3]);
}
System.out.printf(line);
}
}
Вывод:
+------------------------+------------------+----------------+--------+
| ФИО | Должность | Зарплата | Стаж |
+------------------------+------------------+----------------+--------+
| Иванов И.И. | Программист | 95000.50 | 5 |
| Петров П.П. | Дизайнер | 78000.00 | 3 |
| Сидоров С.С. | Менеджер | 85000.75 | 7 |
| Козлова А.В. | Аналитик | 92000.25 | 4 |
+------------------------+------------------+----------------+--------+
Совет: Обратите внимание на шаблон %-22s — минус означает выравнивание по левому краю. Без минуса строка выровнялась бы по правому, и таблица выглядела бы некрасиво. Всегда проверяйте выравнивание, когда создаёте табличные отчёты.
String.format — форматирование без вывода
Метод String.format() делает ровно то же самое, что и System.out.printf(), но вместо вывода в консоль возвращает отформатированную строку. Это非常 удобно, когда вы хотите сохранить результат в переменную, записать в файл или передать другому методу.
Аналогия: printf — как печатник, который сразу печатает документ на принтере. String.format — как словесный редактор, который возвращает вам готовый текст, а вы сами решаете, куда его деть.
Базовое использование
public class StringFormatDemo {
public static void main(String[] args) {
String name = "Александр";
int age = 25;
double gpa = 4.85;
String greeting = String.format("Привет, %s! Вам %d лет.", name, age);
System.out.println(greeting);
String record = String.format("Студент: %s, Возраст: %d, Средний балл: %.2f",
name, age, gpa);
System.out.println(record);
String padded = String.format("[%15s]", name);
System.out.println("Выровненная строка: " + padded);
String withIndex = String.format("%2$s (%1$s)", "RU", "Россия");
System.out.println(withIndex);
}
}
Вывод:
Привет, Александр! Вам 25 лет.
Студент: Александр, Возраст: 25, Средний балл: 4.85
Выровненная строка: [ Александр]
Россия (RU)
Когда использовать String.format вместо printf
Используйте String.format() когда:
- Вы хотите сохранить результат в переменную
- Вы строите SQL-запрос или HTTP-параметры
- Вы создаёте лог-сообщения
- Вы формируете сообщения для исключений
- Вы хотите передать отформатированную строку в метод
Примеры типичного использования:
public class StringFormatUseCases {
public static void main(String[] args) {
int productId = 12345;
double price = 1500.75;
String errorMessage = String.format(
"Ошибка в строке %d: неожиданный символ '%c'",
42, 'x');
System.out.println(errorMessage);
String logMessage = String.format(
"[ERROR] %s — Product %d, price %.2f",
java.time.LocalDateTime.now(), productId, price);
System.out.println(logMessage);
String fileName = String.format("report_%04d_%02d_%02d.txt",
2025, 6, 25);
System.out.println("Имя файла: " + fileName);
String sql = String.format(
"SELECT * FROM products WHERE id = %d AND price < %.2f",
productId, price);
System.out.println("SQL: " + sql);
}
}
Вывод:
Ошибка в строке 42: неожиданный символ 'x'
[ERROR] 2026-06-25T12:00:00.000 — Product 12345, price 1500.75
Имя файла: report_2025_06_25.txt
SQL: SELECT * FROM products WHERE id = 12345 AND price < 1500.75
Аналогия: String.format() — как шаблон письма в Word. Вы вставляете переменные в заготовку, и получаете готовый текст. Этот текст можно отправить по электронной почте, сохранить в файл или распечатать — вы сами решаете.
Конкатенация vs String.format — что выбрать?
Давайте сравним три способа создания строки:
public class Comparison {
public static void main(String[] args) {
String name = "Иван";
int age = 30;
String s1 = "Привет, " + name + "! Возраст: " + age;
String s2 = String.format("Привет, %s! Возраст: %d", name, age);
String s3 = new StringBuilder()
.append("Привет, ")
.append(name)
.append("! Возраст: ")
.append(age)
.toString();
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
System.out.println("Все одинаковые: " + s1.equals(s2) + " " + s2.equals(s3));
}
}
Все три способа дают одинаковый результат. Когда что использовать:
- Конкатенация (+) — для простых случаев, 2-3 переменных. Самый читаемый способ.
- String.format() — когда нужное форматирование (точность, ширина, выравнивание). Идеален для отчётов и логов.
- StringBuilder — для сложных конструкций, циклов, когда создаётся много строк. Самый быстрый способ.
DecimalFormat — гибкое форматирование чисел
Класс DecimalFormat из пакета java.text предоставляет гораздо более гибкие возможности форматирования чисел, чем printf. С его помощью вы можете создавать собственные шаблоны формата: определять, сколько знаков после запятой, как группировать цифры, использовать символы валюты и многое другое.
Аналогия: printf — как стандартные настройки камеры (автоматический режим). DecimalFormat — как ручной режим, где вы сами настраиваете диафрагму, выдержку и ISO.
Основы использования
import java.text.DecimalFormat;
public class DecimalFormatBasic {
public static void main(String[] args) {
double number = 1234567.891;
DecimalFormat df1 = new DecimalFormat("#,###.##");
System.out.println("#,###.## : " + df1.format(number));
DecimalFormat df2 = new DecimalFormat("#,###.00");
System.out.println("#,###.00 : " + df2.format(number));
DecimalFormat df3 = new DecimalFormat("###,###.0000");
System.out.println("###,###.0000: " + df3.format(number));
DecimalFormat df4 = new DecimalFormat("00000000.00");
System.out.println("00000000.00: " + df4.format(number));
DecimalFormat df5 = new DecimalFormat("#.###E0");
System.out.println("#.###E0 : " + df5.format(number));
}
}
Вывод:
#,###.## : 1,234,567.89
#,###.00 : 1,234,567.89
###,###.0000: 1,234,567.8910
00000000.00: 01234567.89
#.###E0 : 1.235E6
Запомните разницу между символами # и 0:
#— показывает цифру только если она есть. Если цифра ведущая и равна нулю, она не отображается.0— всегда показывает цифру. Если её нет, подставляет ноль. Идеально для выравнивания чисел по количеству цифр.
Паттерны формата — подробное разбор
| Символ | Описание | Пример паттерна |
|---|---|---|
0 |
Цифра (обязательная, дополняется нулями) | 000.00 → 042.50 |
# |
Цифра (опциональная) | ##.## → 42.5 |
. |
Десятичный разделитель | #.# → 3.1 |
, |
Разделитель групп (тысячи) | #,### → 1,000 |
E |
Научная нотация | #E0 → 1E6 |
% |
Умножает на 100 и добавляет % | ##.##% → 85.5% |
‰ |
Умножает на 1000 и добавляет ‰ | #‰ → 855‰ |
Форматирование валюты
import java.text.DecimalFormat;
public class CurrencyFormat {
public static void main(String[] args) {
double price = 1234.56;
DecimalFormat rubleFormat = new DecimalFormat("#,##0.00 ¤");
System.out.println("Рубли: " + rubleFormat.format(price));
DecimalFormat dollarFormat = new DecimalFormat("$#,##0.00");
System.out.println("Доллары: " + dollarFormat.format(price));
DecimalFormat euroFormat = new DecimalFormat("#,##0.00 €");
System.out.println("Евро: " + euroFormat.format(price));
DecimalFormat simpleFormat = new DecimalFormat("#,##0.00");
System.out.println("Без символа: " + simpleFormat.format(price));
DecimalFormat priceTag = new DecimalFormat("#,##0 руб.");
System.out.println("Ценник: " + priceTag.format(price));
}
}
Вывод:
Рубли: 1,234.56 ¤
Доллары: $1,234.56
Евро: 1,234.56 €
Без символа: 1,234.56
Ценник: 1,234 руб.
Символ ¤ (валютный знак) — это универсальный символ валюты. Java автоматически заменит его на символ текущей локали. Но вы можете вписать свой символ вручную — как мы сделали с руб., $ и €.
Форматирование процентов
import java.text.DecimalFormat;
public class PercentFormat {
public static void main(String[] args) {
double rate = 0.125;
double discount = 0.073;
double tax = 0.20;
DecimalFormat pct1 = new DecimalFormat("##.##%");
DecimalFormat pct2 = new DecimalFormat("##.0%");
DecimalFormat pct3 = new DecimalFormat("0.00%");
DecimalFormat pct4 = new DecimalFormat("#%");
System.out.println("Ставка: " + pct1.format(rate));
System.out.println("Скидка: " + pct2.format(discount));
System.out.println("Налог: " + pct3.format(tax));
System.out.println("Просто %: " + pct4.format(rate));
double value = 0.8567;
System.out.println("\nТочность процента:");
System.out.println("0.0% : " + new DecimalFormat("0.0%").format(value));
System.out.println("0.00% : " + new DecimalFormat("0.00%").format(value));
System.out.println("0.000% : " + new DecimalFormat("0.000%").format(value));
}
}
Вывод:
Ставка: 12.5%
Скидка: 7.3%
Налог: 20.00%
Просто %: 13%
Точность процента:
0.0% : 85.7%
0.00% : 85.67%
0.000% : 85.670%
Важно: Символ % в паттерне автоматически умножает число на 100. Поэтому 0.125 отображается как 12.5%, а не как 0.125%. Это математически корректно: 12.5% = 0.125.
Специальные символы в паттернах
import java.text.DecimalFormat;
public class SpecialSymbols {
public static void main(String[] args) {
double negative = -1234.56;
DecimalFormat df1 = new DecimalFormat("#,##0.00;(#,##0.00)");
System.out.println("Отрицательное в скобках: " + df1.format(negative));
DecimalFormat df2 = new DecimalFormat("+#,##0.00;-#,##0.00");
System.out.println("С знаком: " + df2.format(negative));
System.out.println("С знаком: " + df2.format(1234.56));
DecimalFormat df3 = new DecimalFormat("#,##0.00 'руб.'");
System.out.println("С текстом: " + df3.format(1234.56));
DecimalFormat df4 = new DecimalFormat("'Цена: ' #,##0.00");
System.out.println("С префиксом: " + df4.format(1234.56));
DecimalFormat df5 = new DecimalFormat("#,##0.00;(#,##0.00);'нет данных'");
System.out.println("Положительное: " + df5.format(1234.56));
System.out.println("Отрицательное: " + df5.format(-1234.56));
System.out.println("Ноль: " + df5.format(0));
}
}
Вывод:
Отрицательное в скобках: (1,234.56)
С знаком: -1,234.56
С знаком: +1,234.56
С текстом: 1,234.56 руб.
С префиксом: Цена: 1,234.56
Положительное: 1,234.56
Отрицательное: (1,234.56)
Ноль: нет данных
Формат положительное;отрицательное;ноль позволяет задать три разных шаблона для разных случаев. Это非常 удобно для финансовых приложений, где отрицательные числа often показываются в скобках.
Разбор ошибок при форматировании
Типичные ошибки начинающих:
import java.text.DecimalFormat;
import java.text.ParseException;
public class FormatMistakes {
public static void main(String[] args) {
double num = 123.456;
System.out.println("Правильно: " + new DecimalFormat("#.##").format(num));
System.out.println("Правильно: " + new DecimalFormat("#.00").format(num));
System.out.println("Два разделителя: " + new DecimalFormat("#.#.#").format(num));
DecimalFormat wrongPattern = new DecimalFormat("abc");
try {
wrongPattern.parse("123");
} catch (ParseException e) {
System.out.println("Невозможно распарсить с этим паттерном");
}
DecimalFormat df = new DecimalFormat("#.##");
System.out.println("Без разделителя групп: " + df.format(1234567.89));
System.out.println("С разделителем: " + new DecimalFormat("#,###.##").format(1234567.89));
}
}
Типичные ошибки:
- Два десятичных разделителя
#.#.#— некорректный паттерн - Забыть разделитель тысяч для больших чисел — читаемость ухудшается
- Использовать
#вместо0когда нужно гарантировать количество цифр - Не учитывать, что
%умножает число на 100
NumberFormat — форматирование по правилам локали
Класс NumberFormat — это ещё один инструмент для форматирования чисел, но с важным отличием: он автоматически учитывает правила локали. В разных странах разные правила записи чисел: запятая или точка как десятичный разделитель, пробел или запятая как разделитель тысяч, символ валюты в начале или в конце.
Аналогия: DecimalFormat — как калькулятор с ручной настройкой. NumberFormat — как калькулятор, который сам определяет, в какой стране вы находитесь, и подставляет нужные настройки.
Получение экземпляра NumberFormat
import java.text.NumberFormat;
import java.util.Locale;
public class NumberFormatBasic {
public static void main(String[] args) {
double number = 1234567.891;
NumberFormat defaultFormat = NumberFormat.getInstance();
System.out.println("По умолчанию: " + defaultFormat.format(number));
NumberFormat russianFormat = NumberFormat.getInstance(new Locale("ru", "RU"));
System.out.println("Русский: " + russianFormat.format(number));
NumberFormat usFormat = NumberFormat.getInstance(Locale.US);
System.out.println("Американский: " + usFormat.format(number));
NumberFormat germanFormat = NumberFormat.getInstance(Locale.GERMANY);
System.out.println("Немецкий: " + germanFormat.format(number));
NumberFormat frenchFormat = NumberFormat.getInstance(Locale.FRANCE);
System.out.println("Французский: " + frenchFormat.format(number));
}
}
Вывод:
По умолчанию: 1,234,567.891
Русский: 1 234 567,891
Американский: 1,234,567.891
Немецкий: 1.234.567,891
Французский: 1 234 567,891
Посмотрите, как одно и то же число форматируется по-разному в зависимости от локали:
- Русский: пробелы как разделитель тысяч, запятая как десятичный разделитель
- Американский: запятые как разделитель тысяч, точка как десятичный разделитель
- Немецкий: точки как разделитель тысяч, запятая как десятичный разделитель
- Французский: пробелы как разделитель тысяч, запятая как десятичный разделитель
Почему это важно? Представьте, что вы пишете онлайн-магазин. Если клиент из России видит цену 1 234,50 ₽, а клиент из Германии видит 1.234,50 € — каждый понимает цену. Но если показать русскому клиенту 1,234.50, он может решить, что это примерно 1 тысяча 234 рубля и 50 копеек, а не 1 тысяча 234 тысячи. Именно для этого существует интернационализация.
Форматирование валюты
import java.text.NumberFormat;
import java.util.Locale;
public class CurrencyFormatting {
public static void main(String[] args) {
double amount = 12345.67;
NumberFormat rubleFormat = NumberFormat.getCurrencyInstance(new Locale("ru", "RU"));
System.out.println("Рубли: " + rubleFormat.format(amount));
NumberFormat dollarFormat = NumberFormat.getCurrencyInstance(Locale.US);
System.out.println("Доллары: " + dollarFormat.format(amount));
NumberFormat euroFormat = NumberFormat.getCurrencyInstance(Locale.GERMANY);
System.out.println("Евро (Германия): " + euroFormat.format(amount));
NumberFormat yenFormat = NumberFormat.getCurrencyInstance(Locale.JAPAN);
System.out.println("Йены (Япония): " + yenFormat.format(amount));
NumberFormat realFormat = NumberFormat.getCurrencyInstance(new Locale("pt", "BR"));
System.out.println("Реал (Бразилия): " + realFormat.format(amount));
}
}
Вывод (может отличаться в зависимости от JDK):
Рубли: 12 345,67 ₽
Доллары: $12,345.67
Евро (Германия): 12.345,67 €
Йены (Япония): ¥12,346
Реал (Бразилия): R$ 12.345,67
Обратите внимание на японские йены — у них нет дробной части, поэтому число округляется до целого. Это соответствует реальной практике: в Японии копейки не используются в повседневных расчётах.
Форматирование процентов
import java.text.NumberFormat;
import java.util.Locale;
public class PercentFormatting {
public static void main(String[] args) {
double rate = 0.1256;
double discount = 0.073;
NumberFormat rusPercent = NumberFormat.getPercentInstance(new Locale("ru", "RU"));
System.out.println("Русский процент: " + rusPercent.format(rate));
NumberFormat usPercent = NumberFormat.getPercentInstance(Locale.US);
System.out.println("Американский процент: " + usPercent.format(rate));
NumberFormat rusPercent2 = NumberFormat.getPercentInstance(new Locale("ru", "RU"));
rusPercent2.setMinimumFractionDigits(2);
System.out.println("Русский с 2 знаками: " + rusPercent2.format(rate));
NumberFormat usPercent2 = NumberFormat.getPercentInstance(Locale.US);
usPercent2.setMinimumFractionDigits(2);
System.out.println("Американский с 2 знаками: " + usPercent2.format(rate));
System.out.println("\nСкидка в России: " + rusPercent.format(discount));
System.out.println("Скидка в США: " + usPercent.format(discount));
rusPercent2.setMinimumFractionDigits(1);
System.out.println("Скидка (точнее): " + rusPercent2.format(discount));
}
}
Вывод:
Русский процент: 13%
Американский процент: 13%
Русский с 2 знаками: 12,56%
Американский с 2 знаками: 12.56%
Скидка в России: 7%
Скидка в США: 7%
Скидка (точнее): 7,3%
Метод setMinimumFractionDigits() позволяет задать минимальное количество знаков после запятой. Это非常 удобно, когда вам нужна определённая точность отображения.
Настройка NumberFormat
import java.text.NumberFormat;
import java.util.Locale;
public class NumberFormatCustom {
public static void main(String[] args) {
double number = 1234.5;
NumberFormat nf = NumberFormat.getInstance(Locale.US);
nf.setMinimumIntegerDigits(6);
System.out.println("Минимум 6 целых цифр: " + nf.format(number));
nf.setMinimumFractionDigits(3);
nf.setMaximumFractionDigits(3);
System.out.println("Ровно 3 знака: " + nf.format(number));
nf.setGroupingUsed(false);
System.out.println("Без разделителя групп: " + nf.format(number));
nf.setGroupingUsed(true);
nf.setMaximumIntegerDigits(4);
System.out.println("Максимум 4 целых цифры: " + nf.format(number));
NumberFormat rf = NumberFormat.getInstance(new Locale("ru", "RU"));
rf.setMinimumIntegerDigits(1);
rf.setMinimumFractionDigits(2);
System.out.println("\nРусский формат: " + rf.format(42.1));
System.out.println("Русский формат: " + rf.format(1000000.5));
}
}
Вывод:
Минимум 6 целых цифр: 001,234.5
Ровно 3 знака: 001,234.500
Без разделителя групп: 001234.500
Максимум 4 целых цифры: 1,234.500
Русский формат: 42,10
Русский формат: 1 000 000,50
Парсинг (обратное преобразование)
NumberFormat不仅能格式ировать, но и распознавать числа из строк:
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;
public class NumberParsing {
public static void main(String[] args) {
NumberFormat russianFormat = NumberFormat.getInstance(new Locale("ru", "RU"));
NumberFormat usFormat = NumberFormat.getInstance(Locale.US);
try {
Number num1 = russianFormat.parse("1 234,56");
System.out.println("Русский парсинг: " + num1.doubleValue());
Number num2 = usFormat.parse("1,234.56");
System.out.println("Американский парсинг: " + num2.doubleValue());
Number num3 = russianFormat.parse("1 000 000");
System.out.println("Миллион: " + num3.doubleValue());
Number num4 = usFormat.parse("1,000,000.00");
System.out.println("Миллион (US): " + num4.doubleValue());
} catch (ParseException e) {
System.out.println("Ошибка парсинга: " + e.getMessage());
}
}
}
Вывод:
Русский парсинг: 1234.56
Американский парсинг: 1234.56
Миллион: 1000000.0
Миллион (US): 1000000.0
Важно: При парсинге обязательно используйте тот же экземпляр NumberFormat, которым вы форматировали число. Если вы форматировали русским форматом, парсите русским. Иначе формат не распознает разделители.
Locale — локаль и интернационализация
Locale — это объект, который определяет язык, регион и культурные особенности. Это кирпичик интернационализации. Locale влияет на всё: форматирование чисел, дат, валют, порядок сортировки строк, даже направление текста.
Аналогия: Locale — как паспорт для вашей программы. Паспорт говорит миру, откуда вы и какие правила для вас применимы. Когда ваша программа «предъявляет паспорт» с локалью ru_RU, все форматирование автоматически подстраивается под российские стандарты.
Создание Locale
import java.util.Locale;
public class LocaleCreation {
public static void main(String[] args) {
Locale ruLocale = new Locale("ru", "RU");
System.out.println("Русский: " + ruLocale);
System.out.println(" Язык: " + ruLocale.getLanguage());
System.out.println(" Страна: " + ruLocale.getCountry());
System.out.println(" Название: " + ruLocale.getDisplayName());
System.out.println(" Название на русском: " + ruLocale.getDisplayName(ruLocale));
System.out.println(" Название на английском: " + ruLocale.getDisplayName(Locale.ENGLISH));
Locale usLocale = Locale.US;
System.out.println("\nАмериканский: " + usLocale);
System.out.println(" Язык: " + usLocale.getLanguage());
System.out.println(" Страна: " + usLocale.getCountry());
Locale germanLocale = Locale.GERMANY;
System.out.println("\nНемецкий: " + germanLocale);
Locale frenchLocale = new Locale("fr", "FR");
System.out.println("\nФранцузский: " + frenchLocale);
Locale japanLocale = Locale.JAPAN;
System.out.println("\nЯпонский: " + japanLocale);
Locale chinaLocale = Locale.CHINA;
System.out.println("\nКитайский: " + chinaLocale);
Locale britishLocale = Locale.UK;
System.out.println("\nБританский: " + britishLocale);
}
}
Вывод:
Русский: ru_RU
Язык: ru
Страна: RU
Название: Russian (Russia)
Название на русском: русский (Россия)
Название на английском: Russian (Russia)
Американский: en_US
Язык: en
Страна: US
Немецкий: de_DE
Французский: fr_FR
Японский: ja_JP
Китайский: zh_CN
Британский: en_GB
Структура кода Locale: языок_СТРАНА. Язык — это двухбуквенный код ISO 639 (например, ru, en, de), страна — двухбуквенный код ISO 3166 (например, RU, US, DE).
Встроенные константы Locale
Java предоставляет удобные константы для самых распространённых локалей:
import java.util.Locale;
public class LocaleConstants {
public static void main(String[] args) {
Locale[] locales = {
Locale.US, Locale.UK, Locale.CANADA,
Locale.GERMANY, Locale.FRANCE, Locale.ITALY,
Locale.JAPAN, Locale.CHINA, Locale.KOREA,
new Locale("ru", "RU"), new Locale("pt", "BR"),
new Locale("es", "ES"), new Locale("ar", "SA")
};
for (Locale loc : locales) {
System.out.printf("%-8s → %-30s (язык: %s, страна: %s)%n",
loc, loc.getDisplayName(), loc.getLanguage(), loc.getCountry());
}
}
}
Вывод:
en_US → English (United States) (язык: en, страна: US)
en_GB → English (United Kingdom) (язык: en, страна: GB)
en_CA → English (Canada) (язык: en, страна: CA)
de_DE → German (Germany) (язык: de, страна: DE)
fr_FR → French (France) (язык: fr, страна: FR)
it_IT → Italian (Italy) (язык: it, страна: IT)
ja_JP → Japanese (Japan) (язык: ja, страна: JP)
zh_CN → Chinese (China) (язык: zh, страна: CN)
ko_KR → Korean (South Korea) (язык: ko, страна: KR)
ru_RU → Russian (Russia) (язык: ru, страна: RU)
pt_BR → Portuguese (Brazil) (язык: pt, страна: BR)
es_ES → Spanish (Spain) (язык: es, страна: ES)
ar_SA → Arabic (Saudi Arabia) (язык: ar, страна: SA)
Locale по умолчанию
import java.util.Locale;
public class DefaultLocale {
public static void main(String[] args) {
Locale defaultLocale = Locale.getDefault();
System.out.println("Локаль по умолчанию: " + defaultLocale);
System.out.println("Язык: " + defaultLocale.getLanguage());
System.out.println("Страна: " + defaultLocale.getCountry());
System.out.println("Название: " + defaultLocale.getDisplayName());
double price = 1234.56;
System.out.printf("Число с локалью по умолчанию: %,f%n", price);
Locale original = Locale.getDefault();
Locale.setDefault(Locale.US);
System.out.printf("Число с US локалью: %,f%n", price);
Locale.setDefault(new Locale("ru", "RU"));
System.out.printf("Число с RU локалью: %,f%n", price);
Locale.setDefault(original);
}
}
Осторожно: Метод Locale.setDefault() изменяет глобальную локаль для всей JVM. Используйте его только в тестах. В реальном приложении передавайте нужную локаль явно в методы форматирования.
Влияние Locale на форматирование дат
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class DateLocalization {
public static void main(String[] args) {
LocalDate date = LocalDate.of(2025, 6, 25);
DateTimeFormatter fullRu = DateTimeFormatter.ofPattern("d MMMM yyyy 'г.'", new Locale("ru", "RU"));
DateTimeFormatter fullUs = DateTimeFormatter.ofPattern("MMMM d, yyyy", Locale.US);
DateTimeFormatter fullDe = DateTimeFormatter.ofPattern("d. MMMM yyyy", Locale.GERMANY);
DateTimeFormatter iso = DateTimeFormatter.ISO_LOCAL_DATE;
System.out.println("Русский: " + date.format(fullRu));
System.out.println("Американский: " + date.format(fullUs));
System.out.println("Немецкий: " + date.format(fullDe));
System.out.println("ISO 8601: " + date.format(iso));
DateTimeFormatter shortRu = DateTimeFormatter.ofPattern("dd.MM.yyyy", new Locale("ru", "RU"));
DateTimeFormatter shortUs = DateTimeFormatter.ofPattern("MM/dd/yyyy", Locale.US);
DateTimeFormatter shortDe = DateTimeFormatter.ofPattern("dd.MM.yyyy", Locale.GERMANY);
System.out.println("\nКраткий формат:");
System.out.println("Русский: " + date.format(shortRu));
System.out.println("Американский: " + date.format(shortUs));
System.out.println("Немецкий: " + date.format(shortDe));
}
}
Вывод:
Русский: 25 июня 2025 г.
Американский: June 25, 2025
Немецкий: 25. Juni 2025
ISO 8601: 2025-06-25
Краткий формат:
Русский: 25.06.2025
Американский: 06/25/2025
Немецкий: 25.06.2025
Видите? Одна и та же дата по-разному записывается в разных культурах:
- Русский: 25 июня 2025 г. (день, потом месяц)
- Американский: June 25, 2025 (месяц, потом день)
- Немецкий: 25. Juni 2025 (день. месяц год)
Получение доступных локалей
import java.util.Locale;
public class AvailableLocales {
public static void main(String[] args) {
Locale[] allLocales = Locale.getAvailableLocales();
System.out.println("Всего доступных локалей: " + allLocales.length);
System.out.println("\nНесколько примеров:");
for (int i = 0; i < Math.min(10, allLocales.length); i++) {
Locale loc = allLocales[i];
System.out.printf(" %-10s → %s%n", loc, loc.getDisplayName());
}
Locale[] frenchLocales = Locale.getAvailableLocales();
System.out.println("\nФранцузские локали:");
for (Locale loc : frenchLocales) {
if (loc.getLanguage().equals("fr")) {
System.out.println(" " + loc + " — " + loc.getDisplayName());
}
}
}
}
MessageFormat — интернационализация сообщений
Класс MessageFormat предназначен для интернационализации текстовых сообщений. Он позволяет создавать шаблоны строк с параметрами, которые подставляются в нужные места. Это非常 удобно для локализации интерфейсов и сообщений.
Аналогия: MessageFormat — как готовое письмо-шаблон, где вместо имени, даты и суммы стоят заглушки {0}, {1}, {2}. Вы просто заполняете заглушки нужными данными, и письмо готово.
Базовое использование
import java.text.MessageFormat;
public class MessageFormatBasic {
public static void main(String[] args) {
String pattern1 = "Привет, {0}! Ваш баланс: {1}";
String result1 = MessageFormat.format(pattern1, "Иван", "1 234,50 руб.");
System.out.println(result1);
String pattern2 = "{0} отправил {1} руб. {2} ({3,choice,1#одному|# {3} получателям})";
String result2 = MessageFormat.format(pattern2,
"Александр", "5000", "Ивану", 1);
System.out.println(result2);
String result3 = MessageFormat.format(pattern2,
"Александр", "5000", "Всем", 5);
System.out.println(result3);
String pattern3 = "Сегодня {0,date,dd.MM.yyyy}, время {1,time,HH:mm}";
String result4 = MessageFormat.format(pattern3,
java.time.LocalDateTime.now(), java.time.LocalDateTime.now());
System.out.println(result4);
}
}
Вывод:
Привет, Иван! Ваш баланс: 1 234,50 руб.
Александр отправил 5000 руб. Ивану (одному)
Александр отправил 5000 руб. Всем (5 получателям)
Сегодня 25.06.2025, время 12:00
Параметры формата MessageFormat
Параметры в фигурных скобках могут содержать специальные типы форматирования:
| Параметр | Описание | Пример |
|---|---|---|
{0} |
Простая подстановка | {0} → "Иван" |
{0,number} |
Формат числа по локали | {0,number} → "1 234,56" |
{0,number,#.##} |
Число с кастомным паттерном | {0,number,#.##} → "1234,57" |
{0,date} |
Формат даты по локали | {0,date} → "25.06.2025" |
{0,time} |
Формат времени по локали | {0,time} → "12:00:00" |
{0,choice,...} |
Выбор формы по числу | 1 файл / 5 файлов |
Формат чисел в MessageFormat
import java.text.MessageFormat;
import java.util.Locale;
public class NumberInMessage {
public static void main(String[] args) {
double price = 1234567.89;
double percent = 0.15;
int count = 42;
String msg1 = "Общая стоимость: {0,number,#,##0.00} руб.";
System.out.println(MessageFormat.format(msg1, price));
String msg2 = "Найдено {0,number,integer} записей";
System.out.println(MessageFormat.format(msg2, count));
String msg3 = "Скидка: {0,number,##.##%}";
System.out.println(MessageFormat.format(msg3, percent));
String msg4 = "Общая стоимость: {0,number,currency}";
System.out.println(MessageFormat.format(msg4, price));
MessageFormat mf = new MessageFormat("");
mf.setLocale(Locale.US);
mf.applyPattern("Total: {0,number,#,##0.00} USD");
System.out.println(mf.format(new Object[]{price}));
}
}
Вывод:
Общая стоимость: 1 234 567,89 руб.
Найдено 42 записей
Скидка: 15%
Общая стоимость: 1 234 567,89 ₽
Total: 1,234,567.89 USD
ChoiceFormat — склонение слов
В русском языке слова склоняются в зависимости от числа: «1 файл», «2 файла», «5 файлов». ChoiceFormat помогает решить эту проблему:
import java.text.MessageFormat;
import java.util.Locale;
public class ChoiceFormatDemo {
public static void main(String[] args) {
String pattern = "Загружено {0,choice,0#ни одного файла|1#один файл|1<{0} файл|2<{0} файлов}";
System.out.println(MessageFormat.format(pattern, 0));
System.out.println(MessageFormat.format(pattern, 1));
System.out.println(MessageFormat.format(pattern, 2));
System.out.println(MessageFormat.format(pattern, 5));
System.out.println(MessageFormat.format(pattern, 21));
System.out.println(MessageFormat.format(pattern, 25));
System.out.println();
String pattern2 = "На счету {0,choice,0#нет денег|1#{0} рубль|1<# {0} рубл|2<{0} рублей}";
System.out.println(MessageFormat.format(pattern2, 0));
System.out.println(MessageFormat.format(pattern2, 1));
System.out.println(MessageFormat.format(pattern2, 2));
System.out.println(MessageFormat.format(pattern2, 5));
System.out.println(MessageFormat.format(pattern2, 21));
System.out.println(MessageFormat.format(pattern2, 25));
}
}
Вывод:
ни одного файла
один файл
2 файл
5 файлов
21 файл
25 файлов
нет денег
1 рубль
2 рубл
5 рублей
21 рубль
25 рублей
Формат синтаксиса choice: 0#текст означает «если число >= 0, показать текст». Символ | разделяет варианты. Символ # означает «показать число» в тексте. Обратите внимание, что полная русская склонение требует более сложных правил, чем простой choice format.
Практический пример: система уведомлений
import java.text.MessageFormat;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class NotificationSystem {
public static void main(String[] args) {
String welcome = "Добро пожаловать, {0}! У вас {1,choice,0#нет сообщений|1#одно сообщение|1<{1} сообщений|2<{1} сообщений}.";
System.out.println(MessageFormat.format(welcome, "Александр", 0));
System.out.println(MessageFormat.format(welcome, "Мария", 1));
System.out.println(MessageFormat.format(welcome, "Пётр", 3));
System.out.println(MessageFormat.format(welcome, "Елена", 25));
System.out.println();
String order = "Заказ #{0} от {1,date,dd.MM.yyyy HH:mm} на сумму {2,number,currency}.";
MessageFormat orderFormat = new MessageFormat(order);
orderFormat.setLocale(new Locale("ru", "RU"));
Object[] orderData = {12345, java.util.Date.from(
java.time.Instant.now()), 15678.90};
System.out.println(orderFormat.format(orderData));
System.out.println();
String delivery = "Доставка {0,choice,0#отменена|1#осуществляется|2#завершена}.";
System.out.println(MessageFormat.format(delivery, 0));
System.out.println(MessageFormat.format(delivery, 1));
System.out.println(MessageFormat.format(delivery, 2));
}
}
Вывод:
Добро пожаловать, Александр! У вас нет сообщений.
Добро пожаловать, Мария! У вас одно сообщение.
Добро пожаловать, Пётр! У вас 3 сообщений.
Добро пожаловать, Елена! У вас 25 сообщений.
Заказ #12345 от 25.06.2025 12:00 на сумму 15 678,90 ₽.
Доставка отменена.
Доставка осуществляется.
Доставка завершена.
Практический пример: форматированный отчёт
Давайте соберём всё изученное в одном большом практическом примере. Создадим программу, которая формирует красивый отчёт о продажах с учётом валюты, процентов и дат.
import java.text.DecimalFormat;
import java.text.MessageFormat;
import java.text.NumberFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class SalesReport {
public static void main(String[] args) {
Locale ru = new Locale("ru", "RU");
NumberFormat currencyFormat = NumberFormat.getCurrencyInstance(ru);
NumberFormat percentFormat = NumberFormat.getPercentInstance(ru);
percentFormat.setMinimumFractionDigits(1);
DecimalFormat intFormat = new DecimalFormat("#,##0");
DecimalFormat priceFormat = new DecimalFormat("#,##0.00");
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("dd MMMM yyyy 'г.'", ru);
DateTimeFormatter timeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss", ru);
LocalDateTime now = LocalDateTime.now();
LocalDate today = now.toLocalDate();
String[][] sales = {
{"Ноутбук HP ProBook", "2", "65000.00", "89.5"},
{"Монитор Samsung 27", "5", "32000.00", "76.3"},
{"Клавиатура Logitech", "12", "4500.00", "95.2"},
{"Мышь Logitech MX", "8", "7800.00", "91.0"},
{"Наушники Sony WH", "3", "28000.00", "82.4"}
};
System.out.println("╔════════════════════════════════════════════════════════════════════╗");
System.out.println("║ ОТЧЁТ О ПРОДАЖАХ ║");
System.out.println("╠════════════════════════════════════════════════════════════════════╣");
System.out.printf("║ Дата: %-56s ║%n", today.format(dateFormatter));
System.out.printf("║ Время: %-56s ║%n", now.format(timeFormatter));
System.out.printf("║ Локаль: %-56s ║%n", ru.getDisplayName());
System.out.println("╚════════════════════════════════════════════════════════════════════╝");
System.out.println();
System.out.println("┌──────────────────────────┬────┬──────────────┬──────────────┬──────┐");
System.out.println("│ Товар │ Кол│ Сумма │ Средняя │ Выпл│");
System.out.println("├──────────────────────────┼────┼──────────────┼──────────────┼──────┤");
double totalSales = 0;
int totalItems = 0;
for (String[] item : sales) {
String name = item[0];
int quantity = Integer.parseInt(item[1]);
double price = Double.parseDouble(item[2]);
double availability = Double.parseDouble(item[3]) / 100;
double itemTotal = quantity * price;
totalSales += itemTotal;
totalItems += quantity;
System.out.printf("│ %-24s │ %2d │ %12s │ %12s │ %4s │%n",
name,
quantity,
currencyFormat.format(itemTotal),
priceFormat.format(price),
percentFormat.format(availability));
}
System.out.println("├──────────────────────────┼────┼──────────────┼──────────────┼──────┤");
System.out.printf("│ ИТОГО │ %2d │ %12s │ │ │%n",
totalItems, currencyFormat.format(totalSales));
System.out.println("└──────────────────────────┴────┴──────────────┴──────────────┴──────┘");
System.out.println();
System.out.println("╔════════════════════════════════════════════════════════════════════╗");
System.out.println("║ СТАТИСТИКА ║");
System.out.println("╠════════════════════════════════════════════════════════════════════╣");
String summary = "Всего продано {0} единиц товара на общую сумму {1}";
System.out.printf("║ %-64s║%n",
MessageFormat.format(summary, totalItems, currencyFormat.format(totalSales)));
String avgMessage = "Средний чек: {0}";
System.out.printf("║ %-64s║%n",
MessageFormat.format(avgMessage, currencyFormat.format(totalSales / totalItems)));
String topItem = sales[0][0];
String topMessage = "Лучший товар по количеству: {0} ({1} шт.)";
System.out.printf("║ %-64s║%n",
MessageFormat.format(topMessage, topItem, sales[2][1]));
System.out.println("╚════════════════════════════════════════════════════════════════════╝");
System.out.println();
System.out.println("Отчёт сформирован " + now.format(DateTimeFormatter.ofPattern(
"dd.MM.yyyy в HH:mm:ss", ru)));
}
}
Программа демонстрирует:
- Использование
NumberFormatдля валюты и процентов с учётом локали - Использование
DecimalFormatдля точного контроля формата чисел - Использование
MessageFormatдля создания шаблонных сообщений - Использование
DateTimeFormatterдля форматирования дат и времени - Создание красивой табличной структуры с помощью
printf
Совет: В реальных проектах данные для отчёта обычно берутся из базы данных. Но принцип форматирования остаётся тем же: берёте «сырые» данные и преобразуете их в читаемый вид с помощью инструментов форматирования.
Сравнение инструментов форматирования
Давайте подведём промежуточные итоги и разберёмся, когда какой инструмент использовать:
| Инструмент | Когда использовать | Особенности |
|---|---|---|
System.out.printf |
Вывод в консоль, отладка | Простой синтаксис, мгновенный вывод |
String.format |
Создание строк, логи, сообщения | Возвращает строку, можно сохранить |
DecimalFormat |
Кастомные шаблоны, валюта, проценты | Гибкие паттерны, собственные символы |
NumberFormat |
Локализованное форматирование | Автоматически учитывает Locale |
MessageFormat |
Интернационализация сообщений | Параметризованные шаблоны с {0}, {1} |
Locale |
Настройка языка и региона | Влияет на все форматирования |
Дополнительные примеры и паттерны
Форматирование времени с SimpleDateFormat
Хотя класс SimpleDateFormat считается устаревшим, он всё ещё широко используется. Рекомендуется использовать DateTimeFormatter из Java 8+, но знание старого API полезно для работы с legacy-кодом:
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class DateTimeFormatting {
public static void main(String[] args) {
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter fullRu = DateTimeFormatter.ofPattern(
"EEEE, d MMMM yyyy 'г.', HH:mm:ss", new Locale("ru", "RU"));
System.out.println("Полный русский: " + now.format(fullRu));
DateTimeFormatter shortRu = DateTimeFormatter.ofPattern("dd.MM.yyyy HH:mm");
System.out.println("Краткий русский: " + now.format(shortRu));
DateTimeFormatter iso = DateTimeFormatter.ISO_DATE_TIME;
System.out.println("ISO 8601: " + now.format(iso));
DateTimeFormatter us = DateTimeFormatter.ofPattern(
"EEEE, MMMM d, yyyy, h:mm a", Locale.US);
System.out.println("Американский: " + now.format(us));
DateTimeFormatter de = DateTimeFormatter.ofPattern(
"EEEE, d. MMMM yyyy, HH:mm 'Uhr'", Locale.GERMANY);
System.out.println("Немецкий: " + now.format(de));
DateTimeFormatter custom = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS");
System.out.println("Кастомный ISO: " + now.format(custom));
}
}
Вывод:
Полный русский: четверг, 25 июня 2025 г., 12:00:00
Краткий русский: 25.06.2025 12:00
ISO 8601: 2025-06-25T12:00:00
Американский: Thursday, June 25, 2025, 12:00 PM
Немецкий: Donnerstag, 25. Juni 2025, 12:00 Uhr
Кастомный ISO: 2025-06-25T12:00:00.000
Паттерны формата даты и времени
| Символ | Описание | Пример |
|---|---|---|
y |
Год | yyyy → 2025, yy → 25 |
M |
Месяц | MM → 06, MMM → июн, MMMM → июнь |
d |
День месяца | dd → 25, d → 5 |
E |
День недели | EEE → чт, EEEE → чт, EEEE → четверг |
H |
Часы (24-часовой) | HH → 13, H → 3 |
h |
Часы (12-часовой) | hh → 01, h → 1 |
m |
Минуты | mm → 05, m → 5 |
s |
Секунды | ss → 09, s → 9 |
a |
AM/PM | a → PM, AM |
Практика: конвертер валют
import java.text.NumberFormat;
import java.text.DecimalFormat;
import java.util.Locale;
import java.util.Scanner;
public class CurrencyConverter {
public static void main(String[] args) {
double usdToRub = 89.50;
double eurToRub = 97.30;
NumberFormat rubFormat = NumberFormat.getCurrencyInstance(new Locale("ru", "RU"));
NumberFormat usdFormat = NumberFormat.getCurrencyInstance(Locale.US);
NumberFormat eurFormat = NumberFormat.getCurrencyInstance(Locale.GERMANY);
DecimalFormat rateFormat = new DecimalFormat("0.00");
System.out.println("╔══════════════════════════════════════╗");
System.out.println("║ КОНВЕРТЕР ВАЛЮТ ║");
System.out.println("╠══════════════════════════════════════╣");
System.out.printf("║ Курс USD/RUB: %s ║%n", rateFormat.format(usdToRub));
System.out.printf("║ Курс EUR/RUB: %s ║%n", rateFormat.format(eurToRub));
System.out.println("╚══════════════════════════════════════╝");
System.out.println();
double amountRub = 10000.50;
double amountUsd = amountRub / usdToRub;
double amountEur = amountRub / eurToRub;
System.out.println("Сумма в разных валютах:");
System.out.println(" Рубли: " + rubFormat.format(amountRub));
System.out.println(" Доллары: " + usdFormat.format(amountUsd));
System.out.println(" Евро: " + eurFormat.format(amountEur));
System.out.println();
String template = "%-12s %15s %15s %15s%n";
System.out.printf(template, "Валюта", "Сумма", "Курс к RUB", "В RUB");
System.out.println("─".repeat(62));
System.out.printf(template, "RUB", rubFormat.format(amountRub), "1.00", rubFormat.format(amountRub));
System.out.printf(template, "USD", usdFormat.format(amountUsd), rateFormat.format(usdToRub), rubFormat.format(amountRub));
System.out.printf(template, "EUR", eurFormat.format(amountEur), rateFormat.format(eurToRub), rubFormat.format(amountRub));
}
}
Этот пример показывает, как:
- Одно и то же значение отображается в трёх разных валютах с помощью
NumberFormat - Таблица форматируется с помощью
printfдля выравнивания колонок - Курсы форматируются с помощью
DecimalFormatдля точного отображения
Часто задаваемые вопросы
В: Когда лучше использовать printf, а когда String.format?
Используйте printf для быстрого вывода в консоль (отладка, тестирование). Используйте String.format, когда нужно сохранить результат в переменную — для логов, сообщений, SQL-запросов.
В: DecimalFormat и NumberFormat — в чём разница?
DecimalFormat даёт полный контроль над форматом через паттерны. NumberFormat автоматически подбирает формат на основе локали. Если вам важна локализация — используйте NumberFormat. Если нужен кастомный формат — DecimalFormat.
В: Что такое интернационализация (i18n)?
Интернационализация — это адаптация программы под разные языки и регионы. Буквы «i18n» означают «internationalization» — 18 букв между «i» и «n». Включает: форматирование чисел, дат, валют, перевод текста, склонение слов.
В: Как правильно обрабатывать ошибки при форматировании?
Всегда используйте try-catch для ParseException при парсинге. Для форматирования ошибок практически нет — неверный паттерн выбросит IllegalArgumentException. Проверяйте паттерны в тестах.
В: Нужно ли всегда учитывать Locale?
Для внутренних программ (инструменты, скрипты) — не обязательно. Для пользовательских приложений — обязательно. Даже если ваша аудитория только в России, числа форматируются по-разному в зависимости от системных настроек пользователя.
Итоги урока
System.out.printf()— вывод отформатированной информации в консоль с использованием спецификаторов (%s, %d, %f, %n и др.), ширины, точности и флагов выравниванияString.format()— аналог printf, но возвращает строку вместо вывода в консоль. Идеален для логов, сообщений и построения строкDecimalFormat— гибкое форматирование чисел через паттерны (#, 0, ,, ., %, E). Позволяет создавать собственные шаблоны для валют, процентов и научной нотацииNumberFormat— форматирование чисел с автоматическим учётом локали. Предоставляет методы getNumberInstance, getCurrencyInstance, getPercentInstanceLocale— объект, определяющий язык и регион. Влияет на все форматирования: числа, даты, валюты, порядок сортировкиMessageFormat— интернационализация сообщений с параметрами {0}, {1}, {2}. Поддерживает форматирование чисел, дат и выбор формы (ChoiceFormat)DateTimeFormatter— форматирование дат и времени с поддержкой локали и кастомных паттернов- Используйте
printfдля вывода,String.formatдля создания строк,NumberFormatдля локализованного форматирования,MessageFormatдля шаблонов - Всегда учитывайте Locale при работе с форматированием, особенно в пользовательских приложениях
Следующий урок
В следующем уроке мы изучим Properties — механизм хранения конфигурационных данных в Java. Вы узнаете, как создавать файлы .properties, читать и записывать настройки, а также использовать ResourceBundle для управления строковыми ресурсами в приложениях с поддержкой нескольких языков.
Модуль 11 · Урок 5 → Properties и ResourceBundle
Тест: Форматирование и интернационализация
10 вопросов