Класс NumberFormat в Java: форматирование чисел, валюты и процентов - Вопросы
Всего: 7 вопросов
1. Что такое класс NumberFormat в Java и как получить его экземпляр?
Что такое класс 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 определяет валюту и почему в выводе может появиться знак ¤?
Валюта определяется по стране локали, а не по языку. Поэтому 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 управляют количеством цифр и какие у них неочевидные эффекты?
Какие методы 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 округляет числа и как форматировать денежные суммы без потери точности?
Как 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?
Как работает метод 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 и когда его использовать?
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 из нескольких потоков?
Нет. NumberFormat и DecimalFormat не потокобезопасны: внутри они хранят изменяемое состояние, и при одновременном вызове format или parse из разных потоков результаты могут перемешаться без всякого исключения.
Варианты решения:
- создавать форматтер внутри метода (каждый вызов фабричного метода возвращает новый объект);
- хранить отдельный экземпляр на поток в
ThreadLocal; - синхронизировать доступ к общему форматтеру.
Поэтому настроенный форматтер в поле static final безопасен только в однопоточном коде.