JavaのString.formatとは?書式指定子の使い方ガイド
CONTENTS
近年のJava開発現場では、可読性が高くメンテナンスしやすいコードが強く求められています。特に、ログ出力や帳票作成、APIレスポンスの整形など、文字列を扱う処理は頻繁に発生します。
そこで役立つのが、Java標準APIのString.formatメソッドです。これは、あらかじめ定義した「型枠(フォーマット)」に値を流し込むことで、整形された文字列を効率的に生成する機能です。
しかし、以下のような疑問を持つ開発者も少なくありません。
- 「書式指定子(
%sや%d)の種類が多くて覚えられない…」 - 「
System.out.printfと何が違うの?」 - 「
+演算子で文字列連結するのと比べて、何がメリットなの?」
この記事では、String.formatの基本的な使い方から、実務で頻出するゼロ埋め・桁数指定・カンマ区切りといった応用テクニック、さらにはパフォーマンスの注意点やJava 8以降のベストプラクティスまで、サンプルコードを交えて網羅的に解説します。
String.formatの基本的な使い方と構文
まず、String.formatがどのような機能なのか、基本的な構文から見ていきましょう。
基本構文と仕組み
String.formatは、Stringクラスに定義されたstaticメソッドです。したがって、インスタンスを生成せずに直接呼び出すことができます。基本構文は以下の通りです。
基本構文:
String result = String.format(String format, Object... args);
- 第1引数 (
format): 書式文字列(テンプレート)を指定します。 - 第2引数以降 (
args): 書式文字列内の「プレースホルダ」に埋め込む可変長引数を指定します。
サンプルコード:
String name = "Taro";
int messages = 5;
String result = String.format("Hello %s, you have %d messages.", name, messages);
System.out.println(result);
実行結果:
Hello Taro, you have 5 messages.
この例では、%s(文字列)がname変数に、%d(10進整数)がmessages変数に置き換えられています。このように、プレースホルダを使ってテンプレートと動的な値を分離することで、コードの可読性が大幅に向上します。
内部的には、String.formatメソッドはjava.util.Formatterクラスを呼び出しています。したがって、書式指定に関する詳細な仕様は、このFormatterクラスのドキュメントに準拠しています。
System.out.printfとの明確な違い
String.formatと非常によく似た機能にSystem.out.printfがあります。一方で、これらは書式指定子のルールを共有していますが、決定的な違いが1つあります。
String.format(...)- 処理結果を文字列 (
String) として返却します。 - そのため、変数への代入、ログファイルへの出力、メソッドの戻り値など、柔軟な使い方が可能です。
- 処理結果を文字列 (
System.out.printf(...)- 処理結果を標準出力(コンソール)に直接出力します。
- 戻り値は
PrintStreamオブジェクトであり、文字列は返しません。 - 主にデバッグやCUIアプリケーションの画面表示用です。
コードでの比較:
double score = 92.456;
// 1. printf: コンソールに出力される
System.out.printf("Score: %.2f%n", score);
// 2. String.format: 文字列として変数に格納される
String logMessage = String.format("Score: %.2f", score);
// この後、ログライブラリ(SLF4Jなど)に渡すことができる
// logger.info(logMessage);
したがって、ログ整形やテストコードのアサーション、APIレスポンス生成など、コンソール出力以外の目的では、常にString.formatを使用します。
なぜ文字列連結(+)より推奨されるのか?
「+演算子で連結すれば良いのでは?」と思うかもしれません。しかし、String.formatには明確なメリットがあります。
非推奨な例(+演算子):
String name = "Yuka";
double score = 92.456;
String msg = name + "のスコアは" + score + "点です";
// -> Yukaのスコアは92.456点です (桁数を制御できない)
推奨される例(String.format):
String msg = String.format("%sのスコアは%.2f点です", "Yuka", 92.456);
// -> Yukaのスコアは92.46点です (桁数を制御し、四捨五入される)
String.formatを使うことで、「小数点以下2桁で表示」「数値が1桁の場合は0で埋める」といった複雑な書式指定を、テンプレート文字列側で完結して定義できます。その結果、ロジックと見た目の定義が分離され、コードの可読性とメンテナンス性が向上します。
【一覧】よく使うフォーマット指定子(%s, %d, %f)
String.formatの使いこなしは、書式指定子(プレースホルダ)の理解にかかっています。さらに、実務で頻繁に使用する指定子を厳選して紹介します。
| 指定子 | 意味 | 主な引数の型 | 使用例 |
|---|---|---|---|
%s |
文字列 | String, Object (toString()が呼ばれる) |
"Hello" |
%d |
10進整数 | int, long, short, byte |
123 |
%f |
浮動小数点数 | double, float |
123.45 |
%b |
真偽値 | boolean |
true / false |
%t |
日付/時刻 (接頭辞) | LocalDate, Calendar, Date |
(後述) |
%c |
文字 | char |
'A' |
%x |
16進整数 | int, long |
ff |
%n |
改行文字 (引数不要) | なし (OS依存の改行コード) | "Line1%nLine2" |
%% |
%文字自体 |
なし (エスケープ) | "100%%" |
1. 文字列 (`%s`)
最も基本的な指定子です。一方で、引数がnullの場合は "null" という文字列が出力されます。
String s = String.format("User: %s", "Alice");
// -> "User: Alice"
2. 整数 (`%d`)
10進数の整数をフォーマットします。したがって、int型やlong型の値を整形する際に使用します。
int count = 100;
String s = String.format("Count: %d", count);
// -> "Count: 100"
3. 浮動小数点数 (`%f`)
doubleやfloat型をフォーマットします。デフォルトでは小数点以下6桁まで表示されます。
double value = 123.456789;
String s = String.format("Value: %f", value);
// -> "Value: 123.456789"
4. 真偽値 (`%b`)
boolean型の値を "true" または "false" という文字列に変換します。なお、nullの場合は "false" になります。
boolean flag = true;
String s = String.format("isValid=%b", flag);
// -> "isValid=true"
5. 改行文字 (`%n`)
\n と似ていますが、%n はOSに依存しないプラットフォーム固有の改行コード(Windowsなら \r\n, Linux/macOSなら \n)を生成します。したがって、ファイル出力など、環境依存を避けたい場合に推奨されます。
String s = String.format("Line 1%nLine 2");
// (Windows環境の場合) -> "Line 1\r\nLine 2"
6. パーセント記号 (`%%`)
"%"という文字自体を出力したい場合は、%% と2つ重ねてエスケープします。
String s = String.format("Discount: 20%%");
// -> "Discount: 20%"
実務で役立つ応用テクニック(フラグと幅指定)
String.formatの真価は、指定子に「フラグ」「幅」「精度」を組み合わせることで発揮されます。さらに、これらはログ整形や帳票出力で必須のテクニックです。
書式指定子の一般構文は以下の要素で構成されます。
%[引数インデックス$][フラグ][幅][.精度]変換文字
(例: "%,10.2f" → カンマ区切り、幅10、小数点2桁の浮動小数点数)
1. ゼロ埋め (`0`フラグ)
指定した幅(桁数)に満たない場合に、先頭を0で埋めます。したがって、IDの採番や日付の整形などで頻繁に使用します。
フラグ: 0 (ゼロ)
構文: %0[幅]d
int id = 42;
// 幅を5桁に指定し、足りない分を0で埋める
String formattedId = String.format("ID:%05d", id);
System.out.println(formattedId);
// 実行結果: ID:00042
2. 左詰め・右詰め (幅と `-`フラグ)
帳票やコンソール出力で、項目の見た目を揃えたい場合に使用します。
フラグ: - (左詰め)
構文: %[幅]s (右詰め) / %-[幅]s (左詰め)
デフォルト(-なし)では右詰めになります。
// デフォルト(右詰め)
String right = String.format("|%10s|", "User");
System.out.println(right);
// 実行結果: | User|
// 「-」フラグで左詰め
String left = String.format("|%-10s|", "User");
System.out.println(left);
// 実行結果: |User |
組み合わせ例(左詰めの文字列と右詰めの数値):
String header = String.format("%-10s | %8s", "ITEM", "PRICE");
String line1 = String.format("%-10s | %8d", "Apple", 120);
String line2 = String.format("%-10s | %8d", "Orange", 95);
System.out.println(header);
System.out.println(line1);
System.out.println(line2);
実行結果:
ITEM | PRICE
Apple | 120
Orange | 95
3. 小数点以下の桁数指定 (`.`精度)
浮動小数点数(%f)に対して、小数点以下の桁数を指定します。したがって、指定した桁数で四捨五入されます。
構文: %.[精度]f
double pi = 3.14159265;
// 小数点以下2桁に指定 (四捨五入される)
String s1 = String.format("PI: %.2f", pi);
System.out.println(s1);
// 実行結果: PI: 3.14
// 小数点以下4桁に指定
String s2 = String.format("PI: %.4f", pi);
System.out.println(s2);
// 実行結果: PI: 3.1416
4. 数値のカンマ区切り (`,`フラグ)
金額や統計データなど、大きな数値を3桁ごとにカンマ(,)で区切りたい場合に使用します。一方で、これはLocale(後述)に依存します。
フラグ: , (カンマ)
構文: %,d (整数) / %,.[精度]f (小数)
long population = 123456789;
String s1 = String.format("%,d", population);
System.out.println(s1);
// 実行結果: 123,456,789
double price = 1000000.5;
String s2 = String.format("%,.2f円", price);
System.out.println(s2);
// 実行結果: 1,000,000.50円
5. 符号の表示 (`+`フラグ)
通常、正の数には符号(+)が付きませんが、+フラグを付けると強制的に表示できます。したがって、増減率などを明確にしたい場合に便利です。
フラグ: + (プラス)
構文: %+d
int gain = 50;
int loss = -20;
String s1 = String.format("Change: %+d", gain);
String s2 = String.format("Change: %+d", loss);
System.out.println(s1);
// 実行結果: Change: +50
System.out.println(s2);
// 実行結果: Change: -20
意外と知らない便利な機能(インデックスとLocale)
基本的な機能に加え、String.formatには国際化対応や複雑な文字列の組み立てに役立つ、より高度な機能が備わっています。
1. 引数のインデックス指定 (`%n$`)
通常、プレースホルダと引数は前から順番に対応します。しかし、「引数の順番と、文字列内での出現順を入れ替えたい」または「同じ引数を何度も使いたい」場合があります。
その場合、%[引数インデックス]$ という形式で、引数の順番(1始まり)を指定できます。
String name = "Taro";
int age = 25;
// 通常 (1番目が%s, 2番目が%d)
String s1 = String.format("%sは%d歳です。", name, age);
// -> Taroは25歳です。
// インデックス指定 (2番目の引数(age)を先に、1番目の引数(name)を後に)
String s2 = String.format("%2$d歳の%1$sさん。", name, age);
// -> 25歳のTaroさん。
// 同じ引数を再利用 (1番目の引数(name)を2回使用)
String s3 = String.format("%1$sさんのニックネームは%1$sです。", name);
// -> TaroさんのニックネームはTaroです。
これは、多言語対応(国際化)の際に、言語によって語順が入れ替わる場合(例: 英語「%s has %d points」 vs 日本語「%sさんの得点は%d点」)に非常に役立ちます。
2. 国際化(i18n)対応と`Locale`指定
実務で最も注意すべき点の一つがLocale(ロケール=国・地域設定)です。例えば、前述のカンマ区切り (%,d) や小数点の記号 (.)、日付の表記は、実行環境のデフォルトロケールに依存します。
例えば、同じコードでも実行環境(開発PCと本番サーバー)のロケール設定が異なると、結果が変わってしまいます。
デフォルトロケールに依存した危険な例:
double value = 1234567.89;
// 「,」フラグと「.」精度
String s = String.format("%,.2f", value);
// もし実行環境(JVM)のデフォルトロケールが Locale.JAPAN なら
// -> 1,234,567.89
// もし実行環境が Locale.GERMANY (ドイツ) なら
// -> 1.234.567,89 (カンマとピリオドが逆転!)
このような意図しない動作を防ぐため、String.formatにはロケールを明示的に指定できるオーバーロード構文が用意されています。
安全な例(Localeを明示的に指定):
import java.util.Locale;
double value = 1234567.89;
// アメリカ形式 (カンマ区切り、ピリオド小数点)
String s_us = String.format(Locale.US, "%,.2f", value);
// -> 1,234,567.89
// ドイツ形式 (ピリオド区切り、カンマ小数点)
String s_de = String.format(Locale.GERMANY, "%,.2f", value);
// -> 1.234.567,89
// 日本形式 (アメリカ形式と同じ)
String s_jp = String.format(Locale.JAPAN, "%,.2f", value);
// -> 1,234,567.89
このように、特にお金や数値を扱うシステムでは、Localeを明示的に指定することを強く推奨します。
Java 8以降の推奨:DateTimeFormatterとの比較
String.formatには日付/時刻を扱うための%t指定子(例: %tY, %tm, %td)も存在します。
// レガシーな java.util.Date を使用
java.util.Date legacyDate = new java.util.Date();
// %tY (年), %tm (月), %td (日)
String s = String.format("%tY/%tm/%td", legacyDate, legacyDate, legacyDate);
// -> 2025/11/09 (実行日)
しかし、この方法はいくつかの問題を抱えています。
- 引数に同じ日付オブジェクトを何度も渡す必要があり冗長(インデックス指定
%1$tY/%1$tm/%1$tdで回避は可能)。 - Java 8より前の古い
DateやCalendarクラスを前提としている。 - 書式指定子(
%tYなど)を覚えるのが直感的ではない。
Java 8以降のベストプラクティス
現在のJava開発(Java 8 LTS以降)では、日付・時刻の扱いはjava.timeパッケージ(LocalDate, LocalDateTimeなど)が標準です。したがって、これらの日付オブジェクトをフォーマットする場合は、String.formatの%tを使うのではなく、専用のDateTimeFormatterクラスを使用することが強く推奨されます。
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
// 1. 現在の日付オブジェクトを取得
LocalDate date = LocalDate.now();
// 2. フォーマットパターンを定義
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd");
// 3. オブジェクトの format メソッドを使用
String formattedDate = date.format(formatter);
System.out.println(formattedDate);
// 実行結果: 2025/11/09 (実行日)
このように、DateTimeFormatterを使うことで、"yyyy/MM/dd" のような直感的で柔軟なパターン指定が可能になります。したがって、日付のフォーマットに関しては、String.formatの%tは避け、DateTimeFormatterに統一するのが現代的なベストプラクティスです。
(外部リンク:Java SE 21 - DateTimeFormatter公式ドキュメント)
パフォーマンスの注意点と代替手法
String.formatは非常に便利ですが、パフォーマンスが要求される場面では注意が必要です。
なぜString.formatは(少し)遅いのか?
String.formatメソッドは、呼び出されるたびに内部で以下の処理を行っています。
-
java.util.Formatterオブジェクトを新規生成(new)。
新規生成(new)。
- 第1引数の書式文字列(フォーマット)をパース(解析)。
- 解析結果に基づいて文字列を構築。
したがって、この「オブジェクト生成」と「書式パース」のコストが、単純な+演算子やStringBuilderによる連結と比較してオーバーヘッドとなります。
NG: ループ内での多用
そのため、forやwhileのループ内でString.formatを繰り返し呼び出すコードは、パフォーマンスのボトルネックになる可能性があります。
避けるべき例(パフォーマンスが悪い):
List<User> users = ...; // 大量のユーザーリスト
String result = "";
for (User user : users) {
// ループの度にFormatterが生成・パースされる
result += String.format("ID: %05d, Name: %s%n", user.getId(), user.getName());
}
推奨: StringBuilderとの使い分け
ループ処理で大量の文字列を連結する場合は、StringBuilderを使用するのが基本です。
推奨される例 (StringBuilder):
List<User> users = ...;
StringBuilder sb = new StringBuilder();
for (User user : users) {
sb.append("ID: ");
sb.append(String.format("%05d", user.getId())); // ←部分的にformatを使うのはOK
sb.append(", Name: ");
sb.append(user.getName());
sb.append("\n");
}
String result = sb.toString();
ただし、上記コードのように「ゼロ埋め」などの複雑な書式指定が必須な箇所では、StringBuilderのappend処理の内部で部分的にString.formatを併用するのが現実的で、可読性も保たれます。
「ループの外側でテンプレートを1回だけ作り、ループの内側では単純な連結(StringBuilder)のみを行う」のが理想的な使い分けです。
よくあるエラーと対策 (`IllegalFormatException`)
String.formatを使用する際、最も遭遇しやすい実行時例外が java.util.IllegalFormatException(またはそのサブクラス)です。
これは、書式文字列(テンプレート)と、渡された引数(データ)が一致しない場合に発生します。
原因1: 引数の数が合わない
プレースホルダの数と引数の数が一致していないケースです。
引数が足りない例:
// %s と %d の2つを要求
String s = String.format("Name: %s, Age: %d", "Taro"); // 引数が1つしかない
// -> MissingFormatArgumentException (引数が足りません)
引数が多すぎる例:
// %s の1つを要求
String s = String.format("Name: %s", "Taro", 25); // 引数が2つある
// -> エラーにはならないが、2番目の引数(25)は無視される
原因2: 引数の型が合わない
プレースホルダが要求する型と、渡された引数の型が一致しないケースです。
型が不一致な例:
// %d (整数) を要求
String s = String.format("ID: %d", "Taro"); // 文字列を渡している
// -> IllegalFormatConversionException (d は java.lang.String に使えません)
String.formatはコンパイル時にはこの不一致をチェックできません(引数がObject...のため)。したがって、実行時に初めてエラーが発覚するため、テンプレートと引数の対応関係は慎重に確認する必要があります。
まとめ|String.formatを使いこなし可読性の高いコードを

本記事では、JavaのString.formatメソッドについて、基本構文から実務的な応用テクニック、パフォーマンスの注意点まで網羅的に解説しました。
String.formatは、単なる文字列連結(+)の代替ではなく、コードの可読性を劇的に向上させ、複雑な書式指定を宣言的に(テンプレートで)行うための強力なツールです。
本記事の重要ポイント振り返り:
String.formatは整形済み文字列を返す。printfはコンソールに出力する。%s(文字列),%d(整数),%f(小数) が基本。%n(改行),%%(%) も重要。- 応用テクニック:
%05d(ゼロ埋め)%-10s(左詰め)%.2f(小数点2桁)%,d(カンマ区切り)
- インデックス指定:
%2$s, %1$sのように引数の順番を入れ替えられる。 - Locale指定:
String.format(Locale.US, ...)のように、数値・通貨を扱う際はロケールを明示的に指定する。 - 日付: Java 8以降は
%tよりもDateTimeFormatterの使用を推奨する。 - 注意点: ループ内での多用はパフォーマンス劣化の原因となる。引数の型や数が合わないと
IllegalFormatExceptionが発生する。
(詳細な仕様確認:Java SE 21 - Formatterクラス公式ドキュメント)
まずは、この記事のサンプルコードを手元の環境で実行し、指定子やフラグを変更して結果がどう変わるかを試してみてください。それが、実務で使えるコード力を身につける一番の近道です。
もしJavaの構文や書式指定で詰まったら、現役エンジニアが伴走する Zerocode Online で実務レベルの開発スキルを体系的に学ぶのも有効です。