S-JIS[2008-05-20/2009-10-21] 変更履歴

Java書式付き出力

Javaで高度な書式を使って、値を編集(変換)する方法。


System.out.printf()・String#format()

C言語のprintf()関数相当の機能がJDK1.5で追加された。

メソッド C言語 備考
PrintStream printf()
format()
printf()相当 ストリームやライターに出力する。
要するにSystem.out.printf()。
PrintWriter printf()
format()
String format() sprintf()相当 Stringを返す。

第一引数で書式を指定し、その書式に則って第二引数以降の値を編集(変換)して出力する。
(第二引数以降の値は、可変長引数で複数指定できる)

	System.out.printf("値1=%d 値2=%d", 123, 456);
	String s = String.format("%04x", 789);

書式には「%」が入った文字列を指定する。「%」の書式指定に従って変換が行われる。
デフォルトでは、複数の%指定がある場合、第二引数以降の値が順番に使われる。
引数が多い分には無視されるだけなので別に構わないが、指定に該当する引数が足りないと例外が発生する。

変換の実体はFormatterクラスにある。
したがってどのような書式があるかについてはFormatterのJavadocに載っているが、代表的なのは以下のようなもの。

C言語と同様の書式指定
書式 説明
% 指定を開始する。「%」自身を出力したい場合は「%%」とする。 printf("%%%n"); %
d 整数を十進数で出力する。(nullの時は「null」) printf("%d%n", 123); 123
o 整数を八進数で出力する。(nullの時は「null」) printf("%o%n", 123); 173
x 整数を十六進数で出力する。(nullの時は「null」) printf("%x%n", 123); 7b
c 文字を出力する。(nullの時は「null」) printf("%c%n", 'x'); x
s 文字列を出力する。(nullの時は「null」)
toString()あるいはFormattableで文字列に変換される)
printf("%s%n", "xyz"); xyz
- 左詰で出力する(省略時は右詰)。 printf("[%-4d]%n", 123);
printf("[%-4s]%n", "xyz");
[123 ]
[xyz ]
0 足りない桁数を0埋めする。
数値系(%d,%o,%x)で使用可能。[2009-10-21]
他(例えば%s,%b,%h)では使用不可(例外が発生する)。
printf("%04d%n", 123);
printf("%04x%n", 123);
0123
007b
# 代替フォームで変換する。
すなわち、「%o」において頭に「0」を、「%x」において頭に「0x」を付けて出力する。
%s」の場合はデフォルトでは特に何も無さそう。「%d」では例外が発生する。
printf("%#o%n", 123);
printf("%#x%n", 123);
printf("%#s%n", 123);
0173
0x7b
123
桁数(最小フィールド幅)を指定する。
最低限確保される桁数であり、この数値以上の桁の値は全部出力される。
%s(文字列)の場合は、文字数である
printf("[%4d]%n", 123);
printf("[%4s]%n", "xyz");
printf("[%4s]%n", "xyzab");
printf("[%4s]%n", "あいう");
[ 123]
[ xyz]
[xyzab]
[ あいう]
.数 精度(最大表示幅)を指定する。
この桁数より大きい場合は切り捨てられ、出力されない。
%s(文字列)の場合は、文字数である
printf("[%.4s]%n", "xyzab");
printf("[%5.4s]%n", "xyzab");
printf("[%-5.4s]%n", "xyzab");
printf("[%-5.4s]%n", "あいうえお");
[xyza]
[ xyza]
[xyza ]
[あいうえ ]
数$ 引数の番号を指定する(値の引数のうち、一番左が1)。
通常これは省略されており、その場合は引数の並び順に従って値が出力される。
C言語とJavaの違い
printf("%d:%d:%d%n",11,22,33);
printf("%1$d:%2$d:%3$d%n",11,22,33);
printf("%3$d:%2$d:%1$d%n",11,22,33);
printf("%3$d:%1$d:%d%n",11,22,33);
11:22:33
11:22:33
33:22:11
33:11:11

Java独自の書式指定
書式 説明
n 機種依存の改行コードを出力する。
C言語では「\n」や「\r\n」を指定していた。つまり機種毎にプログラマーが変えていた。
なお、「\n」「\r」はコンパイルによって改行コードに変換される文字列の一部であるのに対し、
Javaの「%n」は、あくまでprintf()(format())メソッドが解釈する書式指定(の文字列の一部)。
printf("%n");
printf("%s", LINE_SEPARATOR);
 
b 真偽値の文字列を出力する。(nullの時は「false」)
引数がBoolean(boolean)でない場合は常に「true」
printf("%b%n", true);
printf("%b%n", 0);
printf("%b%n", Boolean.FALSE);
printf("%b%n", null);
true
true
false
false
h ハッシュコードを十六進数で出力する。(nullの時は「null」)
C言語なら「%p」に相当するものかな。
printf("%h%n", new Object()); 1d58aae
< 直前の値と同じ値を使用する。($と似た様な機能(→$との兼ね合い)) printf("%d %<d%n", 1); 1 1
t 日付
「t」の後ろにさらに日付書式を指定することで、日付の各要素(年・月・日とか)を出力する。
   
Y 年(4桁) printf("%tY%n", new Date()); 2008
m 月(2桁。0埋め) printf("%tm%n", new Date()); 05
d 日(2桁。0埋め) printf("%td%n", new Date()); 20
H 時(2桁。0埋め) printf("%tH%n", new Date()); 02
M 分(2桁。0埋め) printf("%tM%n", new Date()); 34
S 秒(2桁。0埋め) printf("%tS%n", new Date()); 40
L ミリ秒(3桁。0埋め) printf("%tL%n", new Date()); 953
D 日付(%tm/%td/%tyと同義) printf("%tD%n", new Date()); 05/20/08
F 日付(%tY-%tm-%tdと同義) printf("%tF%n", new Date()); 2008-05-20
T 時刻(%tH:%tM:%tSと同義) printf("%tT%n", new Date()); 02:34:40
日付書式を複数指定する場合、引数もその個数分必要。 Date d = new Date();
printf("%tY/%tm/%td%n", d, d, d);
2008/05/20
ひとつの日時に複数の日時要素を使いたい場合、引数番号を使用するのが楽かも。 Date d = new Date();
printf("%1$tY/%1$tm/%1$td%n", d);
2008/05/20

書式指定文字には大文字小文字で区別があるので、使用する際は注意。
日付書式は全く違うものが割り当てられている。それ以外は、基本的に 書式指定文字が大文字なら値が大文字に変換される。
例えば「%b」はtrue又はfalseになるが、「%B」だとTRUE・FALSEになる。
%S」も、デフォルトでは大文字に変換される。

また、C言語では、書式指定に合致した値を引数に指定する必要があった。
(なので、例えばC言語でlong値を指定する場合は「%ld」のように「long型整数」という指定をする必要があった。
 Java版ではlongでもintでも「%d」でよい(「%ld」という指定をしたら「java.util.UnknownFormatConversionException: Conversion = 'l'」という例外が発生する))
Javaでは引数はObject型なので、その値を書式に合わせて変換してくれる。[/2008-05-21]
(変換できない場合はさすがに例外が発生するけど)

	System.out.printf("%d", 123);	…OK(当然)
	System.out.printf("%s", 123);	…OK!
	System.out.printf("%d", "123");	…java.util.IllegalFormatConversionException
	System.out.printf("%s", "123");	…OK(当然)

※C言語と同じく可変引数部分に数値(プリミティブ型)を置く書き方が出来るが、実際にはオートボクシングによってラッパークラスに変換されるので、実行速度を考えるならそれを意識した方がいいだろう。[2008-05-21]
(オートボクシングでは、例えばintだと、変換先のクラス指定がIntegerの時だけでなく、Objectの時もIntegerに変換してくれる)

	String.format("%d %d %d", 12, 12, 12);
↓実態
	String.format("%d %d %d", Integer.valueOf(12), Integer.valueOf(12), Integer.valueOf(12));

↓効率化を図ったコーディング
	Integer n12 = 12;
	String.format("%d %d %d", n12, n12, n12);

とは言うものの、Formatterによる変換自体(書式文字列の解釈とか)にそれなりのコストがかかるだろうから、そもそも速度にあまり口うるさくないシーンでしか使わない想定かな。


引数番号指定「数$」の動作はC言語とちょっと違うようだ。

  出力
C言語 printf("%3$d:%1$d:%d%n",11,22,33); 33:11:22
Java 33:11:11

C言語では、%1$d(1番目を指定)の後の%d(番号指定なし)は2番目(最後の番号指定の次の番号)の値が使われるのに対し、
Javaでは、n$の指定(番号指定)は全く無視して、番号指定の無いもの同士だけで順番に値が使われるようだ。

一方、「$」と似たような機能である「<」については、忠実に直前の値を使用する。(すなわち、直前が番号指定であろうが無かろうが、直前の%の値を使用する)

	System.out.printf("%d %d %<d %1$d %<s %d %<d %d%n", 11, 22, 33, 44);
	↓
11 22 22 11 11 33 33 44

%sの桁指定について

書式指定の「%s」に桁数精度(最大表示幅)を指定をする場合、文字数のカウントはUNICODEで行われる。[2008-07-29]
すなわち、いわゆる半角文字(文字コード0x7f未満)と全角文字も、文字数は同じ扱いになってしまう。
つまり、等幅のコンソールに出力した場合、たぶん期待している状態にならない。

出力
printf("[%-5s]%n", "abc") [abc  ]
printf("[%-5s]%n", "あいう") [あいう  ]
printf("[%-5s]%n", "あbc") [あbc  ]
printf("[%-5s]%n", "アイウ") [アイウ  ]
printf("[%-5.4s]%n", "abcde") [abcd ]
printf("[%-5.4s]%n", "あいうえお") [あいうえ ]

Formattable

書式指定の「%s」は、基本的に、引数のtoString()を呼んで変換するものと思ってよいだろう。
(nullの場合は"null"になってくれる)

ただし、引数のインスタンスのクラスがFormattableインターフェースを実装している場合は、そのクラスのformatTo()メソッドが呼ばれて変換される。
すなわち、自作クラスをprintf()やformat()の「%s」で出力したい場合、その出力のさせ方(表現方法)を(toString()とは別に)実装することが出来る。

FormattableDate.java:

import java.io.IOException;
import java.util.Calendar;
import java.util.Date;
import java.util.Formattable;
import java.util.Formatter;

public class FormattableDate extends Date implements Formattable {

	@Override
	public void formatTo(Formatter formatter, int flags, int width, int precision) {
		format_test1(formatter);
//		format_test2(formatter);
//		format_test3(formatter);
	}
	/** appendを使って出力する例 */
	void format_test1(Formatter formatter) {
		Calendar cal = Calendar.getInstance();
		cal.setTime(this);
		int yy = cal.get(Calendar.YEAR);
		int mm = cal.get(Calendar.MONTH) + 1;
		int dd = cal.get(Calendar.DATE);
		int hh = cal.get(Calendar.HOUR_OF_DAY);
		int mn = cal.get(Calendar.MINUTE);
		int ss = cal.get(Calendar.SECOND);
		int ms = cal.get(Calendar.MILLISECOND);

		Appendable a = formatter.out();
		try {
			a.append(Integer.toString(yy));
			a.append('/');
			a.append(Integer.toString(mm));
			a.append('/');
			a.append(Integer.toString(dd));
			a.append(' ');
			a.append(Integer.toString(hh));
			a.append(':');
			a.append(Integer.toString(mn));
			a.append(':');
			a.append(Integer.toString(ss));
			a.append('.');
			a.append(Integer.toString(ms));
		} catch (IOException e) {
			throw new RuntimeException(e);
		}
	}

Appendableは文字を出力(追加)する為のインターフェース。Formatter#out()によってインスタンスを取得する。
append()の引数はcharCharSequenceインターフェースしか無いが、StringStringBuilderはCharSequenceをimplementsしているので そのまま渡すことが出来る。

Formatterには、ioException()という「Appendableで最後に起きたI/O例外を返す」メソッドがある。
しかしFormatterにはIOExceptionをセットするメソッドが無い!
なので、Appendable#append()はIOExceptionを投げる可能性がある(throwsで宣言されている)にも関わらず、Formatterにセットする術が無い…。
なぜformatTo()には「throws IOException」が宣言されていないのだろう?
 

	/** formatと%dを使う方式 */
	void format_test2(Formatter formatter) {
		Calendar cal = Calendar.getInstance();
		cal.setTime(this);
		int yy = cal.get(Calendar.YEAR);
		int mm = cal.get(Calendar.MONTH) + 1;
		int dd = cal.get(Calendar.DATE);
		int hh = cal.get(Calendar.HOUR_OF_DAY);
		int mn = cal.get(Calendar.MINUTE);
		int ss = cal.get(Calendar.SECOND);
		int ms = cal.get(Calendar.MILLISECOND);
		formatter.format("%04d/%02d/%02d %02d:%02d:%02d.%03d", yy, mm, dd, hh, mn, ss, ms);
	}

formatTo()の引数であるformatterにもformat()メソッドがある。
自分自身のインスタンス内のそれぞれの値をそこに渡して変換してやればよい。

この例では、もっと簡便に以下の様にすることも出来る。

	/** formatと1$を使う方式 */
	void format_test3(Formatter formatter) {
		formatter.format("%1$tY/%1$tm/%1$td %1$tH:%1$tM:%1$tS.%1$tL", this);
//		formatter.format("%tY/%<tm/%<td %<tH:%<tM:%<tS.%<tL", this);
	}

全ての書式指定に「%1$」という引数番号を指定しているので、(もしくは「%<」という直前の値を使う指定をしているので、)可変引数部は一つだけでよい。
thisを渡しているので「また自分が呼ばれて無限ループになるんじゃないか」と一瞬思ってしまうかもしれないが、formatTo()が呼ばれるのは「%s」のときだけなので、再度呼ばれることは無い。

}
//FormattableDateの終わり


呼び出し例:

	Date date = new FormattableDate();
	System.out.printf("実験結果→%s%n", date);	//この%sの場所でformatTo()が呼ばれる

実行結果:

実験結果→2008/5/20 1:55:5.859   	…test1
実験結果→2008/05/20 01:55:28.078	…test2
実験結果→2008/05/20 01:55:45.359	…test3

formatTo()の引数

Formattable#formatTo()の第二引数以降には、書式の属性が渡されてくる。

引数 flags
フラグ
width
最小文字数
precision
最大文字数
概要 書式指定の属性
大文字・左寄せ等
最小フィールド幅 最大表示幅
%s 0 -1 -1
%5s 0 5 -1
%.4s 0 -1 4
%5.4s 0 5 4
%S UPPERCASE -1 -1
%-5s LEFT_JUSTIFY 5 -1
%#s ALTERNATE -1 -1

widthは、出力する最小文字数。データがこの桁数に足りないときは、スペースで埋めるのが良い。(勝手に埋めてくれたりはしない)

precisionは、出力する最大文字数。データはこの桁数までしか出力しないようにするのが良い。(勝手にカットされたりはしない)

flagsの定数値はFormattableFlagsに定義されており、ビット論理和で合算されている。必要に応じてフラグを判断して編集内容を変えると良い。

	@Override
	public void formatTo(Formatter formatter, int flags, int width, int precision) {

		if ((flags & FormattableFlags.LEFT_JUSTIFY) != 0) {
			// 左寄せの書式指定「-」
			〜
		}
		if ((flags & FormattableFlags.UPPERCASE) != 0) {
			// 大文字の書式指定
			〜
		}
		if ((flags & FormattableFlags.ALTERNATE) != 0) {
			// 代替フォームの書式指定「#」
			〜
		}
		〜
	}

Java目次へ戻る / 新機能へ戻る / 技術メモへ戻る
メールの送信先:ひしだま