🦿

JavaのProcessBuilderで外部アプリ(e.g. Python)と「継続的」に連携する方法

に公開

JavaのProcessBuilderを使用すると外部アプリを起動してその出力を受け取れます。更にそのアプリに入力してデータ処理結果を受け取る事も出来ます。
つまりPythonにしか存在しないライブラリをJavaから使用するといった使い方ができます。
しかし(英語含めて)サンプルコードがあまり無いようなので具体的なコードを紹介します。

ここではPythonとの連携を考えます(実際には他のものにも使えます)。
以下ではコードを単純化するため「文字列を2倍にするメソッド」を実装しています。

# エンコードを明示的にUTF-8にする必要がある
sys.stdout.reconfigure(encoding='utf-8')
sys.stdin.reconfigure(encoding='utf-8')

def python_func(s):
    return s * 3

a = input()
print(python_func(a))

b = input()
print(python_func(b))

Java:

String PYTHON_FILE = "test.py";
//実際に使用する仮想環境のpythonファイル
String PYTHON_EVN = "C:\\SDK\\miniforge-pypy3\\envs\\stanza_interface\\python.exe";
ProcessBuilder pb = new ProcessBuilder(PYTHON_EVN, "-u", PYTHON_FILE);
try {
    Process pythonProcess = pb.start();

    try (var pythonReader = pythonProcess.inputReader()) {
        try (var pythonWriter = pythonProcess.outputWriter()) {
            //外部アプリに「データ」を入力
            pythonWriter.write('d');
            //「入力終了」を知らせるために「改行」という形で区切り文字を入れる
            pythonWriter.newLine();
            //flush()で入力を「反映」
            pythonWriter.flush();

            //外部アプリからの入力を受け取る
            String output1 = pythonReader.readLine();
            System.out.println(output1);//ddd

            //上記と同様
            pythonWriter.write('f');
            pythonWriter.newLine();
            pythonWriter.flush();
            String output2 = pythonReader.readLine();
            System.out.println(output2);//fff
        }
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
} catch (IOException e) {
    throw new RuntimeException(e);
}

なお、外部アプリからデータを受け取る場合、「相手のアプリが今後も出力を続けるつもりか」はJava側では判断できないので、例えば「空行を受け取ったらそこで終了」と事前に決めておいて外部アプリにその処理を加えると同時に、Java側で処理する必要があります。

while (true) {
    line = pythonReader.readLine();
    //空行(Pythonがprint()を実行)が出力されたら終了と判断してループを抜ける
    if (StringUtils.isEmpty(line)) break;

    System.out.println(line);
}

参考: python - Can ProcessBuilder interact(send data and recieve data) with external app(e.g. stanza) continually? - Stack Overflow

追記:
エラー出力がある場合

Thread thread = new Thread(() -> {
    String err;
    try (var errorReader = pythonProcess.errorReader()) {
        while ((err = errorReader.readLine()) != null) {
            System.out.println(err);
        }
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
});
thread.start();

Discussion