ProcessBuilderクラスの使い方

スッキリわかるJava入門（実践編）を読んでいると、**ProcessBuilderクラス**の使い方がちらっと載っていた。
Javaからコマンドプロンプトやターミナルを呼び出す処理は汎用性が高そうだったので、使い方をまとめるのがここでの目標である。

この文は最後に書いているが、ProcessBuilderクラスをまとめる上で、Processクラス、BufferReaderクラス、InputStreamReaderクラスなどとたくさんの出会いがあった。

# ProcessBuilderとは
ProcessBuilderを一言でまとめると、下記である。
> ProcessBuilderとは、Javaから外部プログラムを実行するために使用するクラス。


[ProcessBuilderの使い方](https://techacademy.jp/magazine/19751)
[ProcessBuilderの公式ドキュメント](https://docs.oracle.com/javase/jp/8/docs/api/java/lang/ProcessBuilder.html)

つまり、このクラスはオペレーティング・システムのプロセスの作成に使用される。
例えば、ある計算処理が完了したらメモ帳を起動を行ったり、今のパスに存在するファイル名などを取得したり、など使い方は広くありそうである。

# ProcessBuilderクラスの使い方
下記に使い方を簡単に書く。

1. インスタンスの作成
1. プロセスのスタート
1. 結果を受け取る

上記のように、３ステップに分割される。 １つずつ見ていく前に以下のコードをざっと読んでほしい。(私はMacユーザーなので、Mac使用で書くことを許してほしい。詳しく言うと、ProcessBuilderの中身が多少異なる。理由は後で述べる。）
このコードのことをアルファコードと呼ぶ。

```java
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.nio.charset.Charset;

public class ProcessExecutor {
    public static void main(String[] args) throws Exception {
        // 1. ProcessBuilderインスタンスを生成する
        ProcessBuilder p = new ProcessBuilder("ls", "./");

        // 2. プロセスを開始する
        Process process = p.start();

        // 3. 結果を受け取る
        try (BufferedReader r = new BufferedReader(new InputStreamReader(process.getInputStream(), Charset.defaultCharset()))) {
            String line;
            while ((line = r.readLine()) != null) {
                System.out.println(line);
            }
        }
    }
}

```
アルファコードは、カレントディレクトリに存在するファイル名を表示するプログラムである。(terminalに"ls ./"と入力したものと同じ。）
terminalでは１行で済むのに、Javaを通じてとなるとタスクが増えるな・・・・。:frowning2:

アルファコードを元に

1. インスタンスの作成
1. プロセスのスタート
1. 結果を受け取る

を１つずつ見ていきたい。

### インスタンスの作成
この小節ではメインのProcessBuilderについて深める。
詳しいことは[公式ドキュメント](https://docs.oracle.com/javase/jp/8/docs/api/java/lang/ProcessBuilder.html)を確認してほしい。

ProcessBuilderは何度も述べるが、Javaから外部プログラムを実行するために使用するクラスである。

公式ドキュメントによると、ProcessBuilderのコンストラクタは

```java
ProcessBuilder(List<String> command)
ProcessBuilder(String.. command)
```
の２種類があるよう。

どちらにせよ引数の中身は、Windowsであれば**バッチプログラム**、MacやLinuxであれば**シェルスクリプト**への**命令**である。
引数の内容がMacとWindowsで少し違うのはこれが原因である。コンストラクタは２種類存在し、どちらも同じ働きをする。

引数にはシェルへの命令を記載するのであるが、違いはご覧の通り型の違いである。
String型であれば、一度書いたら変更はインスタンスを作り直すしかないものに対して、List型であれば要素を更新すれば反映されるという違いがある。つまり、String型はList型の簡易メソッドとしても捉えることが出来る。
また、どっちの方式を取ったとしてもcommandが有効なオペレーティング・システム・コマンドに対応するかどうかはチェックされないので注意が必要である。

ちなみに、旧バージョンのJava(〜JDK1.4)では「Runtime」というクラスがProcessBuilderと同じ役割を担っていたが、現在はそれより新しいクラスである「ProcessBuilder」を使うのがよりよい作法とされているらしい。

### プロセスのスタート
ProcessBuilderクラスはメソッドとして"start()"を持っている。
このメソッドは新規プロセスを起動するメソッドで、Processクラスを返す。

Processクラスの役割は
>Processクラスは、プロセスからの入力、プロセスへの出力、プロセス完了の待機、プロセス終了状態の確認、およびプロセスの破棄(終了)を実行するための各メソッドを提供。
[Process公式ドキュメント](https://docs.oracle.com/javase/jp/8/docs/api/java/lang/Process.html)

である。つまり何かを処理している間のあれこれを提供するクラスである。先程のコードにおいては、"ls ./"の命令における処理のプロセス（全体）のあれこれの情報を持っているのである。例えば、上の命令で何が出力されるのか、正常に処理が完了したかなどである。

下記でも紹介するが、Processクラスにおけるよく使われるメソッドは
- exitValu()　（サブプロセスの終了コードを返す。返り値はint型。）
- getInputStream()　（サブプロセスの通常の出力に接続された入力ストリームを返す。返り値は**InputStream型**。詳細は次の小節。）
- isAlive()　（このProcessが表すサブプロセスが生存しているかどうかを判定する。返り値はBoolean型。）
- etc ..

である。

### 結果を受け取る
上記で述べたように、結果を受け取るにはProcessのgetInputStreamメソッドを使用する。このメソッドはInputStream型で返される。
InputStreamクラスはバイト入力ストリームを表現するすべてのクラスのスーパー・クラスである。
[InputStreamクラスの公式ドキュメント](https://docs.oracle.com/javase/jp/8/docs/api/java/io/InputStream.html)

次に結果を読み込むために、バイト・ストリームから文字ストリームへの橋渡しの役目を持つInputStreamReaderクラスを使う。
[InputStreamReaderクラスの公式ドキュメント](https://docs.oracle.com/javase/jp/8/docs/api/java/io/InputStreamReader.html)

このクラスは文字列を読み込む抽象クラスReaderの子クラスである。
このクラスの役割を簡単に言えば、バイトを読み込み、指定されたcharsetを使用して文字に**デコード**するクラスである。

InputStreamReaderのコンストラクタ

```java
InputStreamReader(InputStream in)
デフォルトの文字セットを使うInputStreamReaderを作成。
InputStreamReader(InputStream in, Charset cs)
与えられた文字セットを使うInputStreamReaderを作成。
InputStreamReader(InputStream in, String charsetName)
（文字列で）与えられた文字セットを使うInputStreamReaderを作成。
InputStreamReader(InputStream in, CharsetDecoder dec)
与えられた文字セット・デコーダを使うInputStreamReaderを作成。
```

アルファコードは、2番目のコンストラクタを使って、"ls ./"から得たInputStream型の出力をデフォルトのcharsetでデコードするインスタンスを作成しているのである。

ちなみに、InputStreamReaderクラスのreadメソッドは、バイト入力ストリームから1つ以上のバイトを読み込む。

アルファコードでは、バイトから文字への効率的な変換を可能にするために、BufferedReaderの内部にInputStreamReaderをラップしている。（最初からBufferReder使えよ。と思う方もいると思うが、Readerに対して読込み要求が出されると、それに対応するベースとなる文字型ストリームまたはバイト・ストリームへの読込み要求が発行されるのでこの処理が必要。）
[BufferReaderの公式ドキュメント](https://docs.oracle.com/javase/jp/8/docs/api/java/io/BufferedReader.html)

Bufferについては、ここでは話題とは外れるので自分で調べてほしいが、Bufferを使うことで毎回バイトを読み込んでreadするよりも明らかに速度が速くなるのである。

BufferReaderもReaderの子クラスであるので、read関数close関数を持つ。アルファコードでは、readline関数を使っている。
> readline() : テキスト行を読み込む。


上の３ステップによってProcessBuilderは利用される。このクラスを利用する上で様々な他クラスが出てきたが、いい勉強になった。
今日はこの辺で。

もしミスや質問があれば、コメントしていただけると幸いです。

# 参考資料
- https://techacademy.jp/magazine/19751
- https://docs.oracle.com/javase/jp/8/docs/api/java/lang/ProcessBuilder.html
- https://docs.oracle.com/javase/jp/8/docs/api/java/lang/Process.html
- https://docs.oracle.com/javase/jp/8/docs/api/java/io/InputStream.html
- https://docs.oracle.com/javase/jp/8/docs/api/java/io/InputStreamReader.html
- https://docs.oracle.com/javase/jp/8/docs/api/java/io/BufferedReader.html