Javaで標準入出力の入力元・出力先を変更する方法

java

javaには
・System.setIn
・System.setOut
・System.setErr
の3つのメソッドが用意されており、これらのメソッドにより標準入出力の入力元・出力先を変更することができます。
これらのメソッドを利用することで、標準入力や標準出力を使用している処理の立ち振る舞いを外から変えることができます。

以下は、標準入力をハードコーディングで与え、標準出力・標準エラー出力を文字列として受け取りファイルに出力する例です。

【サンプルコード】

・SystemInOut.java

import java.util.Scanner;

public class SystemInOut {
    public void execute() throws InterruptedException {
        // try-with-resources
        try (Scanner sc = new Scanner(System.in)) {
            String str = sc.nextLine();
            System.out.println("入力された文字:" + str);
            Thread.sleep(500); // 標準出力してから標準エラー出力する
            System.err.println("エラー無し");
        }
    }
}

・SystemInOutMain.java

import java.io.ByteArrayOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;

public class SystemInOutMain {
    public static void main(String[] args)
            throws InterruptedException, IOException {

        // 標準入出力のオブジェクトを退避
        InputStream orgIn = System.in;
        PrintStream orgOut = System.out;
        PrintStream orgErr = System.err;

        // 標準入力の入力元を変更
        class MyInputStream extends InputStream {
            private StringBuilder sb = new StringBuilder();

            // 標準入力したい文字を登録。nextLineに対応するため改行を付与。
            public void typeLine(String str){
                sb.append(str).append(System.getProperty("line.separator"));
            }

            // InputStreamの仕様上readメソッドの実装の必要あり
            @Override
            public int read() {
                if (sb.length() == 0) {
                    return -1; // ストリームの終わり
                }
                int result = sb.charAt(0); // 次に読み込むバイト値
                sb.deleteCharAt(0); // 読み込んだので削除
                return result;
            }
        }
        MyInputStream myIn = new MyInputStream();
        System.setIn(myIn); // 自作の標準入力オブジェクトをセット
        myIn.typeLine("hoge"); // 標準入力に相当する操作

        // 標準出力の出力先を変更
        ByteArrayOutputStream baos1 = new ByteArrayOutputStream();
        PrintStream myOut = new PrintStream(baos1);
        System.setOut(myOut);

        // 標準エラー出力の出力先を変更
        ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
        PrintStream myErr = new PrintStream(baos2);
        System.setErr(myErr);

        // 標準入出力を含む処理を実行
        SystemInOut systemInOut = new SystemInOut();
        systemInOut.execute();

        // 標準出力相当の出力をStringで受け取りファイル出力
        String outStr = new String(baos1.toByteArray());
        FileWriter fw1 = new FileWriter("C:\\tmp\\test1.txt");
        fw1.write(outStr);
        fw1.close();

        // 標準エラー出力相当の出力をStringで受け取りファイル出力
        String errStr = new String(baos2.toByteArray());
        FileWriter fw2 = new FileWriter("C:\\tmp\\test2.txt");
        fw2.write(errStr);
        fw2.close();

        // 後処理
        myIn.close();
        myOut.close();
        baos1.close();
        myErr.close();
        baos2.close();
        System.setIn(orgIn);
        System.setOut(orgOut);
        System.setErr(orgErr);

        // 後処理により標準入出力の入力元・出力先が戻ることも確認済
        // systemInOut.execute();
    }
}

【処理結果】

コンソールは入出力無し

・C:\tmp\test1.txt

入力された文字:hoge

・C:\tmp\test2.txt

エラー無し

いかがでしたでしょうか。

簡易なシステムやレガシーなシステムだと、標準入出力を入出力とするプログラムが少なくないと思います。
そのようなプログラムに対して、外側から立ち振る舞いを変えられるのは便利です。
例えば、JUnitでテストする時に便利だと思いますし、リファクタリングを行う一つの手段にもなり得ると思います。

役に立ちそうな技があれば今後も紹介していきたいと思います!

コメント

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