Класс NumberFormat в Java: форматирование чисел, валюты и процентов - Вопросы

Всего: 7 вопросов

1. 

Что такое класс NumberFormat в Java и как получить его экземпляр?

java.text.NumberFormat — абстрактный класс, который форматирует числа, денежные суммы и проценты по правилам локали (десятичный и групповой разделители, символ валюты, число знаков после запятой) и разбирает такие строки обратно в числа методом parse.

Через new его не создают, а получают фабричными методами:

  • getInstance() / getNumberInstance() — обычное число, по умолчанию от 0 до 3 знаков после запятой;
  • getIntegerInstance() — целое число, дробь округляется;
  • getCurrencyInstance() — денежная сумма с символом валюты;
  • getPercentInstance() — процент, число умножается на 100;
  • getCompactNumberInstance(locale, style) — короткая запись вида 1K, 2M (Java 12+).

У каждого метода есть вариант без аргументов (локаль по умолчанию, то есть настройки ОС) и с параметром Locale. Если результат важен, локаль лучше передавать явно: на сервере она может отличаться от машины разработчика.

2. 

Как getCurrencyInstance определяет валюту и почему в выводе может появиться знак ¤?

Валюта определяется по стране локали, а не по языку. Поэтому getCurrencyInstance(Locale.of("ru", "RU")) выводит 123,46 ₽, а для локали без страны (Locale.of("ru"), Locale.GERMAN) Java не может выбрать валюту и подставляет общий знак денежной единицы ¤ (код XXX): 123,46 ¤. Исключения при этом нет.

Исправить можно двумя способами:

  • передать локаль со страной: Locale.of("ru", "RU"), Locale.GERMANY;
  • задать валюту явно: nf.setCurrency(Currency.getInstance("RUB")).

setCurrency меняет только валюту, разделители и порядок символов остаются от локали форматтера: форматтер Locale.US с валютой EUR выведет €1,234.50. Число знаков после запятой тоже берётся от валюты (у USD, EUR, RUB — 2).

3. 

Какие методы NumberFormat управляют количеством цифр и какие у них неочевидные эффекты?

  • setMaximumFractionDigits(n) — максимум знаков после разделителя, последняя цифра округляется. У getInstance() по умолчанию максимум 3, минимум 0.
  • setMinimumFractionDigits(n) — минимум знаков после разделителя, недостающие дополняются нулями: 123.4567800.
  • setMaximumIntegerDigits(n) — максимум цифр целой части. Старшие цифры молча отбрасываются: при n = 2 число 123.45 выводится как 23.45, без исключения.
  • setMinimumIntegerDigits(n) — ведущие нули. Они считаются обычными цифрами, поэтому разделитель разрядов ставится и между ними: 00,123.
  • setGroupingUsed(false) отключает разделитель разрядов, так получают номера вида 00123.

Если новый максимум меньше текущего минимума, минимум тоже уменьшается, и наоборот.

4. 

Как NumberFormat округляет числа и как форматировать денежные суммы без потери точности?

По умолчанию действует RoundingMode.HALF_EVEN («банковское» округление): ровно половина округляется к ближайшей чётной цифре, поэтому 2.5 даёт 2, а 3.5 — 4. Правило меняется методом setRoundingMode, например setRoundingMode(RoundingMode.HALF_UP).

Округляется точное двоичное значение double, а не литерал из кода: 0.135 хранится как 0.13500000000000000888…, поэтому при двух знаках получится 0.14 даже в режиме HALF_EVEN.

Для денег передавайте в format значение BigDecimal: оно округляется по RoundingMode форматтера без промежуточного double. Для обратного разбора приведите форматтер к DecimalFormat и вызовите setParseBigDecimal(true) — тогда parse вернёт BigDecimal.

5. 

Как работает метод NumberFormat.parse и чем его поведение отличается от Integer.parseInt?

parse(String) разбирает строку по правилам локали форматтера и возвращает Number: Long, если результат целый и помещается в long, иначе Double.

  • Число читается с начала строки, хвост молча игнорируется: parse("12abc") вернёт 12.
  • Если строка не начинается с числа ("abc"), бросается проверяемое java.text.ParseException. С Java 23 строгий режим setStrict(true) делает ошибкой и "12abc".
  • setParseIntegerOnly(true) останавливает разбор на десятичном разделителе: "1234,567" в ru_RU даёт 1234.
  • Одна строка в разных локалях означает разные числа: в US запятая — разделитель разрядов, и "1234,567" превращается в 1234567.

Integer.parseInt и Double.parseDouble не учитывают локаль и на "12abc" бросают непроверяемое NumberFormatException. Сам NumberFormat это исключение не бросает.

6. 

Чем DecimalFormat отличается от NumberFormat и когда его использовать?

DecimalFormat — подкласс NumberFormat, который форматирует число по явному шаблону. Обычно NumberFormat.getInstance() и возвращает DecimalFormat, настроенный по шаблону локали.

Символы шаблона: 0 — обязательная цифра, # — необязательная, , — место разделителя разрядов, . — десятичный разделитель. Например, new DecimalFormat("#,##0.00", symbols).format(1234.5) даёт 1,234.50, а шаблон "000.##" для 5.1 — 005.1.

Какие символы реально подставятся вместо , и ., определяет DecimalFormatSymbols; без него берутся символы локали по умолчанию. DecimalFormat с явными DecimalFormatSymbols нужен, когда формат задан заранее (отчёт, CSV, номер документа) и не должен зависеть от языка пользователя.

7. 

Можно ли использовать один объект NumberFormat из нескольких потоков?

Нет. NumberFormat и DecimalFormat не потокобезопасны: внутри они хранят изменяемое состояние, и при одновременном вызове format или parse из разных потоков результаты могут перемешаться без всякого исключения.

Варианты решения:

  • создавать форматтер внутри метода (каждый вызов фабричного метода возвращает новый объект);
  • хранить отдельный экземпляр на поток в ThreadLocal;
  • синхронизировать доступ к общему форматтеру.

Поэтому настроенный форматтер в поле static final безопасен только в однопоточном коде.

Страница 1 из 1