NumberFormat.format: примеры (JAVA)

Форматирование чисел в Java с NumberFormat.format
Раздел: Форматирование чисел и денежных единиц (NumberFormat)
NumberFormat.format(double number): String

Общее описание NumberFormat.format

Класс java.text.NumberFormat используется для локализованного преобразования чисел в строку и обратно. Метод format форматирует числовое значение в текст согласно настройкам экземпляра NumberFormat или его подклассов (например, DecimalFormat). Типичное применение - отображение чисел, валюты и процентов с учётом локали, количества дробных знаков, разделителей групп и символов валюты.

Основные варианты вызова и поведение:

  • format(double number) - форматирует double и возвращает готовую строку.
  • format(long number) - форматирует long и возвращает строку.
  • format(Object number) - принимает объект, обычно экземпляр Number; при неподходящем типе может выбросить IllegalArgumentException.
  • format(Object number, StringBuffer toAppendTo, FieldPosition pos) - базовый метод из класса Format, который записывает результат в переданный StringBuffer и возвращает его; при этом FieldPosition может использоваться для получения позиции определённого поля (например, целой части).

Настройки NumberFormat, которые влияют на поведение format:

  • Locale - влияет на символы десятичного разделителя, разделителя групп и локализованные символы (например, символ валюты).
  • setGroupingUsed(boolean) - включение/выключение группировки разрядов (разделение тысяч).
  • setMinimumIntegerDigits(int), setMinimumFractionDigits(int), setMaximumFractionDigits(int) - контроль количества цифр в целой и дробной частях.
  • setRoundingMode(java.math.RoundingMode) - режим округления при усечении дробной части.
  • DecimalFormat.applyPattern(String) - у DecimalFormat можно задать собственный шаблон форматирования (например, "#,##0.00 ¤").
  • DecimalFormatSymbols - кастомизация символов (десятичный разделитель, группировщик, символы плюса/минуса и т.д.).

Возвращаемые значения:

  • Для format(double/long/Object) возвращается строка с результатом форматирования.
  • Для format(Object, StringBuffer, FieldPosition) возвращается тот же StringBuffer, в который добавлен результат.

Примечание: NumberFormat является абстрактным; для получения экземпляра обычно используются фабричные методы: NumberFormat.getInstance(), getCurrencyInstance(), getPercentInstance(), а также версии с указанием Locale.

Короткие примеры использования

Примеры показывают разные варианты форматирования. Код и результат приведены отдельно.

1) Простое форматирование числа

import java.text.NumberFormat;
import java.util.Locale;

public class Ex1 {
    public static void main(String[] args) {
        NumberFormat nf = NumberFormat.getInstance(Locale.US);
        String out = nf.format(12345.678);
        System.out.println(out);
    }
}
12,345.678

2) Форматирование валюты

import java.text.NumberFormat;
import java.util.Locale;

public class Ex2 {
    public static void main(String[] args) {
        NumberFormat cf = NumberFormat.getCurrencyInstance(Locale.FRANCE);
        System.out.println(cf.format(12345.678));
    }
}
12 345,68 €

3) Проценты

import java.text.NumberFormat;
import java.util.Locale;

public class Ex3 {
    public static void main(String[] args) {
        NumberFormat pf = NumberFormat.getPercentInstance(Locale.US);
        pf.setMinimumFractionDigits(1);
        System.out.println(pf.format(0.256));
    }
}
25.6%

4) DecimalFormat с шаблоном

import java.text.DecimalFormat;

public class Ex4 {
    public static void main(String[] args) {
        DecimalFormat df = new DecimalFormat("#,##0.00;(#,##0.00)");
        System.out.println(df.format(1234567.89));
        System.out.println(df.format(-1234567.89));
    }
}
1,234,567.89
(1,234,567.89)

5) Использование StringBuffer и FieldPosition

import java.text.FieldPosition;
import java.text.NumberFormat;

public class Ex5 {
    public static void main(String[] args) {
        NumberFormat nf = NumberFormat.getInstance();
        StringBuffer sb = new StringBuffer();
        FieldPosition fp = new FieldPosition(NumberFormat.INTEGER_FIELD);
        nf.format(12345.67, sb, fp);
        System.out.println(sb.toString());
        System.out.println("Integer field begins at: " + fp.getBeginIndex());
    }
}
12,345.67
Integer field begins at: 0

Похожие Java-решения и их особенности

  • DecimalFormat - конкретная реализация NumberFormat с поддержкой шаблонов. Предпочтительнее при необходимости точного шаблонного форматирования (префиксы, суффиксы, разные шаблоны для положительных/отрицательных значений).
  • String.format / Formatter - форматирование через форматы типа C ("%,.2f"). Удобно для быстрого вывода без локализованных символов и при форматировании в рамках printf-подобного синтаксиса.
  • MessageFormat - форматирование сообщений с подстановкой локализованных чисел; подходит для комбинированных строк и локализации сообщений.
  • BigDecimal.toPlainString и методы BigDecimal - используются для точной работы с десятичными значениями, когда важна арифметическая точность; затем можно применять DecimalFormat для представления.

Выбор зависит от требований: для локализации и готовых валют/процентов - NumberFormat; для сложных шаблонов и символов - DecimalFormat; для простого форматирования в стиле printf - String.format.

Аналоги в других языках и отличия

Кратко о популярных альтернативах и примерах кода с результатами.

PHP: number_format

echo number_format(12345.678, 2, '.', ',');
12,345.68

Отличие: простой вызов, не локализованный по умолчанию; для локали используется setlocale/NumberFormatter из intl.

JavaScript: Intl.NumberFormat

console.log(new Intl.NumberFormat('de-DE', { style: 'currency', currency: 'EUR' }).format(12345.678));
12.345,68 €

Отличие: встроенная поддержка локали в рантайме, асинхронные особенности отсутствуют, API похож на Java NumberFormat.

Python: format / locale / babel.numbers

import locale
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')
print(locale.format_string('%.2f', 12345.678, grouping=True))
12,345.68

Отличие: стандартный модуль locale зависит от системных локалей; библиотека Babel даёт более гибкую локализацию.

SQL (пример SQL Server): FORMAT

SELECT FORMAT(12345.678, 'N2', 'en-US');
12,345.68

Отличие: форматирование на стороне БД, может быть медленнее при больших объёмах.

C#: ToString с форматами и CultureInfo

using System;
using System.Globalization;

Console.WriteLine(12345.678.ToString("N2", CultureInfo.GetCultureInfo("fr-FR")));
12 345,68

Отличие: встроенная культура и форматы .NET, поведение близко к Java NumberFormat.

Go: fmt и message.Printer

import (
  "fmt"
  "golang.org/x/text/message"
)

func main() {
  p := message.NewPrinter(message.MatchLanguage("en"))
  p.Printf("%,.2f", 12345.678)
}
12,345.68

Отличие: базовый fmt не локализует разделители, для локализации нужна внешняя библиотека.

Kotlin

import java.text.NumberFormat
import java.util.Locale

fun main() {
  val nf = NumberFormat.getCurrencyInstance(Locale.JAPAN)
  println(nf.format(12345.0))
}
¥12,345

Отличие: использует Java API напрямую, синтаксис Kotlin короче.

Lua

-- стандартный string.format
print(string.format("%.2f", 12345.678))
12345.68

Отличие: нет встроенной локализации разделителей; нужны сторонние библиотеки.

Типичные ошибки при использовании

  • Попытка форматировать null через формат, принимающий Object, приводит к исключению (NullPointerException или IllegalArgumentException в зависимости от реализации).
  • Неправильный тип в format(Object) - если передать не Number, может быть выброшено IllegalArgumentException.
  • Неправильный шаблон в DecimalFormat.applyPattern - приводит к IllegalArgumentException при разборе шаблона.
  • Неправильная настройка min/max fraction digits - если setMaximumFractionDigits меньше, чем setMinimumFractionDigits, поведение может быть неожиданным; обычно следует сначала задать минимум и максимум согласованно.
  • Проблемы многопоточности - экземпляры NumberFormat не потокобезопасны; одновременное использование одного экземпляра из разных потоков может привести к неверному форматированию. Рекомендуется создавать отдельные экземпляры или использовать ThreadLocal.

Примеры ошибок

1) Передача неподходящего объекта

import java.text.NumberFormat;

public class Err1 {
    public static void main(String[] args) {
        NumberFormat nf = NumberFormat.getInstance();
        System.out.println(nf.format("text"));
    }
}
Exception in thread "main" java.lang.IllegalArgumentException: Cannot format given Object as a Number

2) Многопоточность (демонстрация некорректного поведения)

// Упрощённый пример: одновременное изменение настроек и форматирование
import java.text.NumberFormat;

public class Err2 implements Runnable {
    private static final NumberFormat nf = NumberFormat.getInstance();
    public void run() {
        for (int i = 0; i < 1000; i++) {
            nf.setMinimumFractionDigits(i % 5);
            nf.format(12345.678);
        }
    }
    public static void main(String[] args) throws Exception {
        new Thread(new Err2()).start();
        new Thread(new Err2()).start();
    }
}
(поведение неопределено: возможны неверные строки или повреждение внутренних данных)

Изменения и эволюция API

API NumberFormat и DecimalFormat исторически стабильны и сохраняют обратную совместимость. В новых релизах JDK происходили следующие направления развития:

  • Расширение набора локалей и улучшение соответствия стандартам локализации (CLDR). Это влияет на вывод символов и шаблонов для разных регионов.
  • Появилась и улучшалась поддержка дополнительных стилей форматирования (например, компактное представление чисел в некоторых релизах JDK). Доступность таких возможностей зависит от конкретной версии JDK.
  • Улучшена интероперабельность с новыми API локализации и временем выполнения (включая интеграцию с java.time в части локализованных сообщений).

Рекомендуется проверять релиз-ноты конкретной версии JDK для точной информации о новых возможностях и изменениях в поведении форматирования.

Расширенные и редкие варианты использования

Подробные примеры показывают контроль формата, использование FieldPosition для подсветки частей строки, кастомизацию DecimalFormatSymbols, безопасное использование в многопоточной среде и парсинг.

1) Кастомные символы и группы разрядов

Пример java
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.Locale;

public class Adv1 {
    public static void main(String[] args) {
        DecimalFormatSymbols syms = new DecimalFormatSymbols(Locale.US);
        syms.setDecimalSeparator(',');
        syms.setGroupingSeparator(' ');
        DecimalFormat df = new DecimalFormat("#,##0.000", syms);
        System.out.println(df.format(1234567.89123));
    }
}
1 234 567,891

2) Форматирование BigDecimal с управлением округлением

Пример java
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;

public class Adv2 {
    public static void main(String[] args) {
        BigDecimal value = new BigDecimal("2.3455");
        DecimalFormat df = new DecimalFormat("0.00");
        df.setRoundingMode(RoundingMode.HALF_UP);
        System.out.println(df.format(value));
        df.setRoundingMode(RoundingMode.DOWN);
        System.out.println(df.format(value));
    }
}
2.35
2.34

3) Получение атрибутов форматирования (позиции символов)

Пример java
import java.text.AttributedCharacterIterator;
import java.text.FieldPosition;
import java.text.NumberFormat;
import java.text.AttributedCharacterIterator.Attribute;

public class Adv3 {
    public static void main(String[] args) {
        NumberFormat nf = NumberFormat.getCurrencyInstance();
        StringBuffer sb = new StringBuffer();
        FieldPosition fp = new FieldPosition(NumberFormat.INTEGER_FIELD);
        nf.format(9876.54, sb, fp);
        System.out.println(sb.toString());
        // Получение атрибутов через formatToCharacterIterator
        AttributedCharacterIterator it = nf.formatToCharacterIterator(9876.54);
        it.first();
        do {
            System.out.print(it.current());
        } while (it.next() != AttributedCharacterIterator.DONE);
        System.out.println();
    }
}
9,876.54
(пример вывода символов с атрибутами; конкретный вывод зависит от локали)

4) ThreadLocal для потокобезопасности

Пример java
import java.text.NumberFormat;
import java.util.Locale;

public class Adv4 {
    private static final ThreadLocal nf = ThreadLocal.withInitial(() -> NumberFormat.getInstance(Locale.US));
    public static String format(double v) {
        return nf.get().format(v);
    }
    public static void main(String[] args) {
        System.out.println(format(12345.678));
    }
}
12,345.678

5) Парсинг с контекстом и контроль ошибок

Пример java
import java.text.NumberFormat;
import java.text.ParsePosition;

public class Adv5 {
    public static void main(String[] args) {
        NumberFormat nf = NumberFormat.getInstance();
        ParsePosition pp = new ParsePosition(0);
        Number n = nf.parse("1234abc", pp);
        System.out.println(n); // разобранная часть
        System.out.println("Index after parse: " + pp.getIndex());
    }
}
1234
Index after parse: 4

6) Встраивание форматирования в шаблон сообщений

Пример java
import java.text.MessageFormat;
import java.util.Locale;

public class Adv6 {
    public static void main(String[] args) {
        Object[] objs = {12345.678};
        MessageFormat mf = new MessageFormat("Сумма: {0,number,##,##0.00}", Locale.getDefault());
        System.out.println(mf.format(objs));
    }
}
Сумма: 12,345.68

Эти примеры демонстрируют контроль над локалью, шаблонами, округлением, безопасное использование в многопоточном окружении и получение метаданных о форматированной строке.

джава NumberFormat.format function comments

En
NumberFormat.format Formats a number as a string