System.in, System.out, System.errを再初期化する
またかなり間が空いてしまいましてすみません。
今さら感あふれるネタですが、知らなかったのでメモ。
結論を先に書きます。
下記ページで知りました。
- java - Resetting Standard output Stream - Stack Overflow http://stackoverflow.com/questions/5339499/resetting-standard-output-stream
System.in
, System.out
, System.err
の再初期化は、下記のようにします。
FileDescriptor
を使うのがポイントです。
// import java.io.*; System.setIn(new BufferedInputStream(new FileInputStream(FileDescriptor.in))); System.setOut(new PrintStream(new FileOutputStream(FileDescriptor.out))); System.setErr(new PrintStream(new FileOutputStream(FileDescriptor.err)));
ただし、これだけでは完全ではありません。理由は後述します。
元々入っているのを退避しておいて、後で戻す方法でも、もちろんOKです。
これについても後述します。
どういう時に使う?
使う場面は限られていて、単体テスト時や、ツール内で標準出力をリダイレクトする場合などに使用します。IDEのランチャーや、Webアプリケーションコンテナーなどで(たぶん)使われています。通常のアプリケーションではまず使用しません。
単体テストの場合については、JUnitで戻り値がvoid
のメソッドをテストしたい、という要望はわりとあって、System.out.println(String)
の結果をassert
させる方法として、
import static org.junit.Assert.*; import java.io.*; import org.junit.*; public final class HelloTest { @Before public void setUp() throws Exception { } @After public void tearDown() throws Exception { // TODO ここでSystem.outを元に戻したい } @Test public void testPrintMessage() { // デフォルトを退避 PrintStream defaultSystemOut = System.out; // 差し替える ByteArrayOutputStream bos = new ByteArrayOutputStream(); PrintStream ps = new PrintStream(bos); System.setOut(ps); // assert Hello.printMessage(); assertEquals("Hello.", bos.toString()); // 元に戻す System.setOut(defaultSystemOut); } }
のようにしてSystem.out
を書き換えたりすることがあります。
問題は、これを元に戻す方法です。
JUnitの場合なら戻さなくても問題になることはあまり無いとは思いますが、レアなケースで元に戻す必要がある時にどうすれば良いのかを考えました。
退避する方法は確実ではありますが、毎回退避するのも、どこに退避しておくかを考えるのも、面倒です。
今回の方法は、要するに「グローバル変数を使って再初期化ができるよ!」ということです。
Systemクラス初期化時はどうなっているのか?
前述の方法で上手く行っているようですが、この方法で本当にSystemクラス初期化時と同じになっているのでしょうか。
OpenJDK 8u40によれば、
java.lang.System(L.1188) - GrepCode
- (引用)
FileInputStream fdIn = new FileInputStream(FileDescriptor.in); FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out); FileOutputStream fdErr = new FileOutputStream(FileDescriptor.err); setIn0(new BufferedInputStream(fdIn)); setOut0(newPrintStream(fdOut, props.getProperty("sun.stdout.encoding"))); setErr0(newPrintStream(fdErr, props.getProperty("sun.stderr.encoding")));
となっています。
setIn0
, setOut0
, setErr0
は、セキュリティーマネージャーのチェックの有無を除けばsetIn
, setOut
, setErr
と同じですので問題ないでしょう。
newPrintStream
とシステムプロパティーのところを追加する必要がありそうです。
実装する
以上を踏まえて、それぞれの再初期化を実装し、ユーティリティークラスにまとめました。
System.in
,System.out
,System.err
を再初期化するメソッドの実装例
import static java.lang.System.*; import java.io.*; public final class UnofficialSystemUtils { private UnofficialSystemUtils() { } public static void resetIn() { setIn(new BufferedInputStream(new FileInputStream(FileDescriptor.in))); } public static void resetOut() { setOut(newPrintStream(new FileOutputStream(FileDescriptor.out), getProperty("sun.stdout.encoding"))); } public static void resetErr() { setErr(newPrintStream(new FileOutputStream(FileDescriptor.err), getProperty("sun.stderr.encoding"))); } private static PrintStream newPrintStream(FileOutputStream fos, String enc) { if (enc != null) { try { return new PrintStream(new BufferedOutputStream(fos, 128), true, enc); } catch (UnsupportedEncodingException e) { } } return new PrintStream(new BufferedOutputStream(fos, 128), true); } }
これで初期状態と同じになるはずです。
(おわり)