概要
業務システムでは、金額をカンマ区切りで表示する、通貨記号を付ける、小数点以下の桁数を揃えるといった数値フォーマットの処理が頻繁に発生します。Java の NumberFormat と DecimalFormat はこうした要件に対応できますが、ロケールによって小数点とカンマの意味が逆転すること、DecimalFormat のパターン文字列の書き方、BigDecimal との組み合わせ方など、実装時に迷うポイントが少なくありません。この記事では、カンマ区切り整数、小数桁指定、通貨フォーマット、カンマ区切り文字列のパースといった実務で必要になる処理を整理し、ロケールの違いが及ぼす影響と、フォーマット結果を逆変換する際の注意点も押さえます。
使いどころ
請求書や帳票で金額をカンマ区切り(1,234,567円)に整形して出力する
CSV エクスポート時に数値を DecimalFormat のパターン指定で小数2桁に統一し、受領側のインポート仕様に合わせる
画面やファイルから受け取ったカンマ区切り文字列("1,234,567")を NumberFormat.parse で数値に戻し、計算ロジックに渡す
コード例
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));
}
}Version Coverage
var による型推論と formatted() メソッド(Java 15+)でコードが簡潔になる。機能面での変更はないが、記述量が減る。
// 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
注意点
NumberFormat.parse は文字列の先頭から解析し、解析できなくなった位置で止まる。"1,234abc" をパースすると 1234 を返してエラーにならないため、入力値の形式チェックは別途行うこと
DecimalFormat のパターン "#,##0.00" は小数2桁に四捨五入する。BigDecimal の精度を保ちたい場合は、RoundingMode を明示的に設定するか、BigDecimal.setScale で事前に丸めておく
ロケールによって小数点とカンマの役割が逆になる(ドイツでは 1.234.567,89)。国際化対応が必要な場合は Locale を明示的に指定し、デフォルトロケールに依存しない実装にする
NumberFormat はスレッドセーフではない。マルチスレッド環境で共有する場合は ThreadLocal で保持するか、メソッド内で毎回生成する
FAQ
DecimalFormat.format は内部で double に変換するため、桁数が多い場合に精度が落ちることがあります。BigDecimal.toPlainString で文字列化してから整形するか、setRoundingMode で丸め方を指定してください。
仕様上、parse は解析可能な先頭部分だけを変換します。厳密にチェックしたい場合は ParsePosition を渡し、解析後の位置が文字列末尾と一致するか確認します。
単純なゼロ埋めや桁揃えには String.format、カンマ区切りや通貨記号、ロケール依存のフォーマットには NumberFormat が適しています。両者を組み合わせることも可能です。