【Java・実例あり】printf() の使い方

API・ライブラリ

Javaの System.out.printf() メソッドについて解説します。

本記事では、書式指定子および書式指定文字列については深入りしていません。必要な場合は、これらを解説している記事を別途参照してください。

記事内のコードは OpenJDK 21 で動作確認済みです。

System.err.printf() は解説していません。

基本情報

種類インスタンスメソッド
所属クラスjava.io.PrintStream
修飾子public
引数第一引数※1:(書式指定)文字列
第二引数以降※2:すべての参照型
戻り値PrintStream
処理内容引数を書式指定ルールに従って文字列に埋め込み、整形済み文字列を標準出力 out に出力する
APIリファレンスPrintStream (Java SE 21 & JDK 21)
※1 第一引数の前に Locale インスタンスを取ることも可能
※2 第一引数に通常の文字列を指定した場合は省略可能

基本的な使い方

基本的な構文は以下の通りです。

System.out.printf(表示したい書式指定文字列, 値1, 値2, …);

printf() 自体の動作は実にシンプルで、「値を書式付きで表示する」だけです。より具体的には、「書式指定文字列中書式指定子に、指定した値を適切に埋め込み、出力する」動作となります。

書式指定子とは

書式指定シーケンスプレースホルダとも呼ばれ、printf() などの書式付き出力で、値をどのような形式で整形するかを指示する記法のことです。Javaでは %… で表されます。

代表的なものとして %s, %d, %f などがあります。

エスケープシーケンスと書式指定子は別物です。混同しないように注意しましょう。
エスケープシーケンスについて、詳しくは以下の記事を参照してください。

書式指定文字列とは

フォーマット文字列とも呼ばれ、書式指定子を1つ以上含む文字列のことです。
以下はすべて書式指定文字列です。

  • "あなたは%d歳です"
  • "彼の好物は%sと%s"
  • "円周率は%.2fである"

言語仕様としてこのような文字列型が定義されているわけではなく、便宜的にそう呼ばれるだけです。そのため、基本的な扱いは通常の文字列と変わりません。

書式指定子および書式指定文字列について、詳しくは以下の記事を参照してください。

書式指定文字列を利用して整形した文字列を表示する

基本的な printf() の使い方になります。

以下は、もっとも単純な printf() のコードとその実行結果です。

コード
public class Main {
    public static void main(String[] args) {
        System.out.printf("彼は%d歳だ", 10); // 書式指定文字列, 値
    }
}
実行結果
彼は10歳だ

このとき、printf() は次のように動作します。

  1. 書式指定文字列を解析し、書式指定子を探す(今回は %d のみ)
  2. %d に 値 10埋め込む
  3. 埋め込み後の文字列、つまり "彼は10歳だ" を表示する

代表的な書式指定子には以下のようなものがあり、それぞれ埋め込める(扱える)値の種類が異なります。

書式指定子埋め込める値
%s文字列
%d整数
%f小数
※ 実際にはほぼすべての値を埋め込むことが可能

簡単な例を紹介しましたが、これは print("彼は" + "10" + "歳だ") と等価です。わざわざ printf() を使う必要性はないように感じられます。

しかし、printf() では、以下のように書式指定子にオプションを追加することで、値の表示形式を自由に整形することができます。これが最大の強みです。

コード
public class Main {
    public static void main(String[] args) {
        // %d に 04 というオプションを追加
        // 04 は「値を4桁表示にし、空き領域は0埋めする」ことを表すオプション
        System.out.printf("彼は%04d歳だ", 10);
    }
}
実行結果
彼は0010歳だ

このとき、printf() は次のように動作します。

  1. 書式指定文字列を解析し、書式指定子を探す
  2. %04dオプションにしたがって、値 100010 にする
  3. %04d に 値 0010 を埋め込む
  4. 埋め込み後の文字列、つまり "彼は0010歳だ" を表示する

代表的なオプションには以下のようなものがあり、書式指定子によって使用できるオプションも異なります。

オプション意味使用できる書式指定子の種類
(整数値)表示桁(文字)数数値または文字列を扱うもの
0空き領域を0埋め数値を扱うもの
+符号を強制表示数値を扱うもの

また、オプションは複数組み合わせることもできます(%04d(整数値)0 の組み合わせ)。

オプションはその性質によりさらに分類することもできますが、ここでは扱いません。

オプションを使わないと…

先ほどのコードをオプションを使わずに書くと、以下のようになります。

public class Main {
    public static void main(String[] args) {
        int age = 10;
        var strAgeLen = String.valueOf(age).length(); // ageの文字列としての長さ
        var prefix = strAgeLen >= 4 ? "" : "0".repeat(4 - strAgeLen); // 0埋めの文字列
        System.out.printf("彼は" + prefix + "%d歳だ", age);
    }
}

実行結果は同じですが、明らかにコードが長く、何をしているのか分かりにくくなっています。

オプションを使うことで、複雑な整形プロセスをコードから排し、コードをスッキリ保つことができます。これが、オプションが非常に強力な理由です。


printf() には一度に複数の書式指定子と値を指定することもできます。

コード
public class Main {
    public static void main(String[] args) {
        // %s と %d を同時指定
        System.out.printf("彼の名前は%sで、%d歳だ", "ジェームズ", 10);
    }
}
実行結果
彼の名前はジェームズで、10歳だ

このとき、printf() は次のように動作します。

  1. 書式指定文字列を解析し、書式指定子を探す(今回は %s%d の2つ)
  2. %s と %d に値を先頭から順に埋め込む"ジェームズ"%s に、10%d に埋め込まれる)
  3. 埋め込み後の文字列、つまり "彼の名前はジェームズで、10歳だ" を表示する

一括ではなく、先頭から順に埋め込む点に注意しましょう。

「一括か、順番か」を実務で意識する必要はほとんどありませんが、知っておくとエラー発生時に「どの書式指定子でエラーが出たのか」を特定しやすくなります。


ここで一度、printf() の動作を再確認しておきましょう。

  1. 書式指定文字列を解析し、書式指定子を探す
  2. 書式指定子にオプションがあれば、それに従って値を整形する
  3. 書式指定子に整形した値を埋め込む
  4. 2~3を、すべての書式指定子について順に繰り返す
  5. 埋め込み後の文字列を表示する
printf() を使わないと…

printf() を使わずに同じような処理を実現したい場合は、文字列結合を使用します。

しかし、文字列結合にはコードが冗長になるという欠点があります。

public class Main {
    public static void main(String[] args) {
        String name = "真奈美";
        String topic = "食べ物";
        String target = "スイートコーン";
        // 文字列結合の利用
        System.out.print(name + "が好きな" + topic + "は" + target + "です");
    }
}

上のコードは "真奈美が好きな食べ物はスイートコーンです" と表示するものですが、一目見ただけでは何が表示されるのか分かりにくいです。

さらに、文字列結合はその回数が増えるほど処理が遅くなります。オプションも当然使えないため、柔軟な整形もできません。

これらの欠点を加味すると、printf() は非常に使い勝手がよいことが分かります。値を並べて表示したい場合は、まず printf() を検討するとよいでしょう。

文字列を表示する(非推奨)

例外的に、通常の文字列を渡すことで、その文字列を表示することができます。println()print() と同じような使い方です。

「通常の文字列」とは、書式指定子を含まない文字列のことです。

コード
public class Main {
    public static void main(String[] args) {
        System.out.printf("Hello World!\n"); // 通常の文字列
        System.out.printf("I'm Java! Nice to meet you!");
    }
}
実行結果
Hello World!
I'm Java! Nice to meet you!

これは以下のコードと等価です。

public class Main {
    public static void main(String[] args) {
        System.out.println("Hello World!");
        System.out.print("I'm Java! Nice to meet you!");
    }
}

なお、この使い方では値を指定してもすべて無視されます。書式指定子が含まれない場合、printf() は「値を埋め込む」処理に進まないためです。

コード
public class Main {
    public static void main(String[] args) {
        // 10, 100, "太郎" はすべて無視される
        System.out.printf("Hello World!", 10, 100, "太郎");
    }
}
実行結果
Hello World!

あくまでも無視するだけで、エラーにはならない点には注意してください。


この使い方では書式指定子を利用しないため、printf() の良さを完全に潰すことになります。さらに、書式指定子が文字列に含まれていなくとも文字列解析は行われるので、println()print() と比較すると理論上は処理が遅くなります。

以上より、printf() を通常の文字列表示で使うのは非推奨です。println()print() で代用しましょう。

応用的な使い方

メソッドチェーンを利用する

この項の理解には、オブジェクト指向プログラミング(OOP)に関する基本的な理解と習熟が必要です。

println()print() と異なり、System.out.printf() はつねに呼び出し元の PrintStream インスタンスを戻り値として返します。例えば、System.out(初期状態では標準出力)に対して呼び出した場合は、System.out 自身を返します。

呼び出し元の PrintStream インスタンスと戻り値の PrintStream インスタンスは同一(同じインスタンス)です。

これにより、printf() 同士でメソッドチェーンを行えます。

コード
public class Main {
    public static void main(String[] args) {
        System.out.printf("Hello ")
                .printf("World") // メソッドチェーン
                .printf("!")
    }
}
実行結果
Hello World!

これは以下のコードと等価です。

public class Main {
    public static void main(String[] args) {
        System.out.printf("Hello ") // 1命令ずつ記述
        System.out.printf("World")
        System.out.printf("!")
    }
}

戻り値が System.out と同一であることは以下のコードで確認できます。

コード
import java.io.PrintStream;

public class Main {
    public static void main(String[] args) {
        PrintStream ps;

        ps = System.out.printf("Equal? "); // 戻り値の PrintStream インスタンスを代入
        System.out.println(ps == System.out); // ps と System.out が同一かどうか
    }
}
実行結果
Equal? true

ただし、メソッドチェーンを使用してもパフォーマンスが向上することはなく、可読性も下がるため、あまり使われません。豆知識として頭の片隅に置いておく程度で十分でしょう。

printf() のメソッドチェーンは、println()print() とも組み合わせることができます(これらも PrintStream のインスタンスメソッドであるため)。

ただし、println()print() には戻り値がないため、それ以上チェーンを繋げることはできない点に注意しましょう。

よくある間違い

改行の入れ忘れ

printf() は自動で改行しません。そのため、改行したい場合は、\n または %n(推奨)を使用します。

コード
public class Main {
    public static void main(String[] args) {
        System.out.printf("彼は%&d歳です\n", 20);
        System.out.printf("彼は%sが好きです%n", りんご); // 推奨

        System.out.print("彼と仲良くしてあげてください");
    }
}
実行結果
彼は20歳です
彼はりんごが好きです
彼と仲良くしてあげてください

%n が推奨される理由は、OSに合わせた改行コードを出力してくれる(移植性が高い)ためです。

改行コードは、WindowsではCRLF、Linux / macOSではLFなど、OSによって異なります。

例えば、Linuxで動かしていたJavaプログラムをWindowsに移す場合、\n では改行コードの違いによる不具合(改行されないなど)が生じる可能性がありますが、%n はそうしたトラブルを未然に防いでくれます。

%n は書式指定子なので、println()print() では使用できません。

\n は絶対にダメなのか

結論から言うと、ほとんどの場面では、\n でも問題になりにくいです。現代のOSや実行環境は非常に賢く、改行コードの違いを吸収してくれることが大半なためです。

ただし、文字列をファイルに保存するなど、一部の場面では問題が顕在化することがあります。\n および改行コードの不整合について、詳しくはこちらをご覧ください。

書式指定子と値の数が不一致

書式指定子と値の数が一致していないと、予期せぬ挙動になります。

値が過剰な場合

過剰分の値はすべて無視されます。エラーにはなりません。

コード
public class Main {
    public static void main(String[] args) {
        System.out.printf("彼は%d歳です", 10, "りんご"); // "りんご" が過剰
    }
}
実行結果
彼は10歳です

値が不足な場合

エラー(実行時例外:java.util.MissingFormatArgumentException)が発生します。

コード
public class Main {
    public static void main(String[] args) {
        System.out.printf("彼は%d歳で、%sが好きです", 10); // %s に対応する値が不足
    }
}
実行結果
Exception in thread "main" java.util.MissingFormatArgumentException: Format specifier '%s'
	at java.base/java.util.Formatter.format(Formatter.java:2796)
	at java.base/java.io.PrintStream.implFormat(PrintStream.java:1367)
	at java.base/java.io.PrintStream.format(PrintStream.java:1346)
	at java.base/java.io.PrintStream.printf(PrintStream.java:1245)
	at exercise.Main.main(Main.java:5)

環境によっては「彼は10歳で、」までなら表示されることがあります。ただし、これは保証された挙動ではなく、常に表示されるとは限りません。


どちらも、意外と見落としがちなミスです。注意しましょう。

書式指定子と値の型が不一致

%s, %b および値をとらない書式指定子以外の書式指定子には、埋め込める値の型が厳密に定められています。それ以外を埋め込もうとするとエラー(実行時例外:IllegalFormatConversionException)が発生します。

例えば、%d は整数型しか埋め込めないため、小数型等ではエラーになります。

コード
public class Main {
    public static void main(String[] args) {
        System.out.printf("%d", 3.14); // 小数型(double)を埋め込もうとしている
    }
}
実行結果
Exception in thread "main" java.util.IllegalFormatConversionException: d != java.lang.Double
	at java.base/java.util.Formatter$FormatSpecifier.failConversion(Formatter.java:4534)
	at java.base/java.util.Formatter$FormatSpecifier.printInteger(Formatter.java:3072)
	at java.base/java.util.Formatter$FormatSpecifier.print(Formatter.java:3027)
	at java.base/java.util.Formatter.format(Formatter.java:2797)
	at java.base/java.io.PrintStream.implFormat(PrintStream.java:1367)
	at java.base/java.io.PrintStream.format(PrintStream.java:1346)
	at java.base/java.io.PrintStream.printf(PrintStream.java:1245)
	at Main.main(Main.java:5)

代表的な書式指定子が埋め込める型は以下の通りです。

書式指定子埋め込める値の型
%f(小数)float, double, BigDemical など
%d(整数)byte, short, int, long, BigInteger など
%c(文字)char, byte, short など

文字列以外を表示させようとする

println()print() とは異なり、printf() では第一引数に(書式指定)文字列以外を渡すことはできません。

コード
public class Main {
    public static void main(String[] args) {
        System.out.printf(10); // int
        System.out.printf(2.71); // double
        System.out.printf('Z'); // char
    }
}
コンパイル結果(抜粋)
java: no suitable method found for printf(int)
    method java.io.PrintStream.printf(java.lang.String,java.lang.Object...) is not applicable
      (argument mismatch; int cannot be converted to java.lang.String)
    method java.io.PrintStream.printf(java.util.Locale,java.lang.String,java.lang.Object...) is not applicable
      (argument mismatch; int cannot be converted to java.util.Locale)

文字列以外を表示したい場合は、書式指定子を使用するか、println()print()で代用しましょう。

Q&A

System.out.println(), System.out.print() との違いは何ですか?

書式指定文字列を利用できるかどうかです。

System.out.println(), System.out.print() では書式指定子を利用できません。
それ以外にも、引数を複数とれない・文字列以外を直接表示できるなどの違いがあります。単に値を確認したいだけならこちらで十分で、わざわざ printf() を使う必要はありません。

System.err.printf() との違いは何ですか?

出力先(表示先)が異なります。

System.out 系は標準出力 out に文字列を出力し、System.err 系は標準エラー出力 err に文字列を出力します。
ただし、多くの場合で両者は同じ出力先(同一のコンソールなど)を指します。どちらを使っても結果に変化はないことが多いです。
※ 一部の環境では、両者を区別するために表示順や文字色が変化することがあります。
out および err の中身(出力先)は後から変更可能です。

System.out.printf() は自動で改行しますか?

しません。

改行したい場合は、\n または %n を使用します。詳しくはこちらをご覧ください。

System.out.printf()System.out.println() よりも遅いですか?

はい。

println() は改行の付加だけですが、printf() は文字列解析と値の整形・埋め込み処理を行うため、全体的な処理は遅くなります。ただし、現代の環境でこの差が問題になることは稀です。

System.out.printf() の出力先を変えることはできますか?

できます。

System.out は静的変数であり、System.setOut() セッターによって変更可能です。
PrintStream クラスのインスタンスを渡すことができ、この方法で変数 out の内容を変更すれば、出力先を変えることができます。
ただし、変数 out を使うすべての処理に影響するため、注意が必要です。

System.out.printf()null を渡すとどうなりますか?

どのように渡したかによって挙動が異なります。

null だけ渡した場合:NullPointerException 例外(nullString として解釈される)
%s, %bnull を渡した場合:エラーは発生せず、正常に動作
それ以外の書式指定子に渡した場合:書式指定子の実装に依存

実行環境が変わったら表示結果も変わってしまいました。なぜでしょうか?

ロケール(地域情報)によるものです。

printf() は内部で Locale(地域情報を表すクラス)を使用しており、実行環境の地域によって整形ルールを調整することがあります。
例えば、地域によって、小数点の記号が .(ピリオド)であったり ,(カンマ)であったりします。常に同じ地域の表現を使用したい場合は、printf()Locale インスタンスを渡すことができます。
こちらについては、いずれ解説を追加する予定です。

コメント

PAGE TOP
タイトルとURLをコピーしました