概要

業務システムでは、金額をカンマ区切りで表示する、通貨記号を付ける、小数点以下の桁数を揃えるといった数値フォーマットの処理が頻繁に発生します。Java の NumberFormat と DecimalFormat はこうした要件に対応できますが、ロケールによって小数点とカンマの意味が逆転すること、DecimalFormat のパターン文字列の書き方、BigDecimal との組み合わせ方など、実装時に迷うポイントが少なくありません。この記事では、カンマ区切り整数、小数桁指定、通貨フォーマット、カンマ区切り文字列のパースといった実務で必要になる処理を整理し、ロケールの違いが及ぼす影響と、フォーマット結果を逆変換する際の注意点も押さえます。

使いどころ

請求書や帳票で金額をカンマ区切り(1,234,567円)に整形して出力する

CSV エクスポート時に数値を DecimalFormat のパターン指定で小数2桁に統一し、受領側のインポート仕様に合わせる

画面やファイルから受け取ったカンマ区切り文字列("1,234,567")を NumberFormat.parse で数値に戻し、計算ロジックに渡す

コード例

NumberFormatUtil.java
import java.math.BigDecimal;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;

public class NumberFormatUtil {

    /** カンマ区切り整数(日本ロケール) */
    public static String formatInteger(long number) {
        return NumberFormat.getNumberInstance(Locale.JAPAN).format(number);
    }

    /** DecimalFormat パターン指定 */
    public static String formatDecimal(BigDecimal amount, String pattern) {
        return new DecimalFormat(pattern).format(amount);
    }

    /** 通貨フォーマット(¥ マーク付き) */
    public static String formatCurrency(long amount) {
        return NumberFormat.getCurrencyInstance(Locale.JAPAN).format(amount);
    }

    /** カンマ区切り文字列 → long にパース */
    public static long parseFormattedNumber(String str) throws ParseException {
        return NumberFormat.getNumberInstance(Locale.JAPAN)
                .parse(str).longValue();
    }

    /** レポート行の組み立て */
    public static String buildReportLine(String label, BigDecimal value) {
        return "%s: %s円".formatted(label, formatDecimal(value, "#,##0"));
    }

    public static void main(String[] args) throws ParseException {
        // カンマ区切り
        System.out.println(formatInteger(1234567));       // 1,234,567
        System.out.println(formatInteger(-9876543));       // -9,876,543

        // DecimalFormat
        System.out.println(formatDecimal(
            new BigDecimal("12345.678"), "#,##0.00"));     // 12,345.68

        // 通貨
        System.out.println(formatCurrency(9800));          // ¥9,800

        // パース
        System.out.println(parseFormattedNumber("1,234,567")); // 1234567

        // レポート行
        System.out.println(buildReportLine(
            "売上合計", new BigDecimal("1234567")));       // 売上合計: 1,234,567円

        // ロケール別の違い
        double d = 1234567.89;
        System.out.println("日本 : "
            + NumberFormat.getNumberInstance(Locale.JAPAN).format(d));
        System.out.println("米国 : "
            + NumberFormat.getNumberInstance(Locale.US).format(d));
        System.out.println("ドイツ: "
            + NumberFormat.getNumberInstance(Locale.GERMANY).format(d));
    }
}

Java 8 / 17 / 21 の完全なサンプルコードは GitHub リポジトリ で確認できます。

Version Coverage

var による型推論と formatted() メソッド(Java 15+)でコードが簡潔になる。機能面での変更はないが、記述量が減る。

Java 17
// Java 17: var + formatted() で簡潔に
var nf = NumberFormat.getNumberInstance(Locale.JAPAN);
String formatted = nf.format(1234567); // 1,234,567
// formatted() でテンプレート的な書き方
String report = "%s: %s円".formatted(
        "売上合計", nf.format(1234567));

Library Comparison

標準 API(NumberFormat / DecimalFormat)カンマ区切り、通貨表示、小数桁指定など、基本的な数値フォーマット。外部依存なしで対応したいとき。スレッドセーフでない点と、parse の挙動に注意が必要。
Apache Commons Text(FormattableUtils)Formattable インターフェースを使った高度な書式制御が必要な場合。標準の NumberFormat で十分なケースがほとんど。導入メリットは限定的。
ICU4J(NumberFormatter)多言語対応の数値フォーマットや、通貨名の完全なローカライズが必要な場合。JAR サイズが大きく、日本語環境の金額表示だけなら標準 API で事足りる。

注意点

NumberFormat.parse は文字列の先頭から解析し、解析できなくなった位置で止まる。"1,234abc" をパースすると 1234 を返してエラーにならないため、入力値の形式チェックは別途行うこと

DecimalFormat のパターン "#,##0.00" は小数2桁に四捨五入する。BigDecimal の精度を保ちたい場合は、RoundingMode を明示的に設定するか、BigDecimal.setScale で事前に丸めておく

ロケールによって小数点とカンマの役割が逆になる(ドイツでは 1.234.567,89)。国際化対応が必要な場合は Locale を明示的に指定し、デフォルトロケールに依存しない実装にする

NumberFormat はスレッドセーフではない。マルチスレッド環境で共有する場合は ThreadLocal で保持するか、メソッド内で毎回生成する

FAQ

BigDecimal と DecimalFormat を組み合わせるときの注意点は。

DecimalFormat.format は内部で double に変換するため、桁数が多い場合に精度が落ちることがあります。BigDecimal.toPlainString で文字列化してから整形するか、setRoundingMode で丸め方を指定してください。

NumberFormat.parse はなぜ途中で止まっても例外にならないのですか。

仕様上、parse は解析可能な先頭部分だけを変換します。厳密にチェックしたい場合は ParsePosition を渡し、解析後の位置が文字列末尾と一致するか確認します。

String.format と NumberFormat の使い分けはどうしますか。

単純なゼロ埋めや桁揃えには String.format、カンマ区切りや通貨記号、ロケール依存のフォーマットには NumberFormat が適しています。両者を組み合わせることも可能です。

関連書籍

この記事のテーマをさらに深く学びたい方へ。

※ Amazon アソシエイトリンクを含みます