System.outの深淵
Javaでは、System.outを用いて標準出力を行う。このSystem.outはリファレンスを見るとPrintStream型の変数であることがわかる。PrintStreamは任意のOutputStreamをラッピングして、表示を簡単にするための機能(printlnとか)を提供する出力ストリームだ。つまり、内部になんらかのOutputStreamを保持していることになる。
では、System.outが内部に保持しているOutputStreamは一体どのようなOutputStreamなのだろうか?StandardOutputStreamのような隠れたクラスが存在しているのではないか?もしくは、デフォルトでは画面出力用のDisplayOutputStreamのようなものが用意されており、設定によってそれを切り替えて(例えば、サーブレットのときはHttpOutputStreamのようなものに)使われているのだろうか?
そのような期待を持ってSystem.outのお腹の中をさぐってみた。
まずは、JDKに付属しているSystemクラスのソースコードを見てみる。System.outは次のように定義されていた。
public final static PrintStream out = nullPrintStream();
どうやら、nullPrintStream()というメソッドによって初期化されているらしい。Systemクラスに、そんな名前の可視なメソッドはないので、おそらくprivateメソッドなのだろう。
Systemクラスの中を引き続き探してみると、やはりprivateなnullPrintStreamメソッドが見つかる。
private static PrintStream nullPrintStream() throws NullPointerException { if (currentTimeMillis() > 0) return null; throw new NullPointerException(); }
!!!!!!!!!!!!!!!!!
nullをreturnしている??!
このメソッドの中に書かれているのは、nullをreturnするか、もしくはNullPointerExceptionをthrowする、ということだけだ。System.outはfinalで定義されているので、このコードを見ている限り、System.outがnull以外のものになる可能性はない。
しかし、もちろんそんなわけはない。System.outがnullならSystem.out.println()はぬるぽだ。
ソースコードからの解析は無理っぽいので、デバッガを使って調べてみることにしてみた。
デバッガを起動させてSystem.outの中身を覗いてみると、内部にoutという変数を保持していることがわかる。どうやらこのoutはBufferedOutputStreamのようだ。BufferedOutputStreamも、任意のOutputStreamにバッファリング機能を加える出力ストリームなので、まだその腹の中にOutputStreamを抱えているわけだ。
当然、次はBufferedOutputStreamの中を覗いてみる。そこにはやはり、変数outが用意されている。これこそが、System.outの奥深くに隠された、標準出力ストリームの真の姿!!!その正体は?!
え?FileOutputStream?
そっかー、そーだよなー。PHPでも
<?php $out=fopen('php://stdout', 'w'); ?>
とかやるもんなー。
・・・。
なんておもしろみのない結果だ・・・。
調べてみた結果、FileDescriptor.outが標準出力ストリームのファイル記述子となっているようだ。つまり、
// 最後のtrueはautoFlushをtrueにするため。 PrintStream out = new PrintStream(new BufferedOutputStream( new FileOutputStream(FileDescriptor.out)), true); out.println("Hello World!!");
とすることで、System.outと同じPrintStreamのoutを生成し、用いることができる。これを使わなければならないようなシチュエーションが思いつかないけど・・・。
ちなみに、FileDescriptor.outの正体をソースコードから探ってみると、
public static final FileDescriptor out = standardStream(1);
で定義されており、standardStreamメソッドは、
private static FileDescriptor standardStream(int fd) { FileDescriptor desc = new FileDescriptor(); desc.handle = set(fd); return desc; }
となっている。このsetメソッドを見ると、
private static native long set(int d);
となり、nativeメソッドの壁に阻まれてしまったことも併せて報告しておく。