直感的なCollection作成とStream処理の改善
これまでよりも、CollectionやStream処理の記述が簡単になりました。
直感的なCollection作成
ListやSet、もしくはMapといったコレクションのオブジェクトが、リスト1のように簡単に作成できるようになりました。
// (1) 追加するアイテムを順番に記述する final Set<Object> of = Set.of("a","b","c"); final List<String> list = List.of("l1","l2","l3"); // (2) Map形式はKey,Value,Key,Valueのように続けて記述することが可能 final Map<String,String> map1 = Map.of("k1","v1","k2","v2"); // (3) このように記述することも可能です。 final Map<String,String> map2 = Map.ofEntries(Map.entry("k1","v1"),Map.entry("k2","v2"));
(1)の通り、かなり直感的な記述ができるようになっています。また、Map形式の場合には(2)のようにKeyとValueを順番につなげることもできます。また少々面倒になりますが、この記述方法以外にも(3)のように記述することもできます。
リスト1ではコレクション作成時にfinalをつけて宣言していますが、この宣言の有無にかかわらず、作成したコレクションの変更はできません。
Stream処理の改善
Stream処理でも便利なメソッドがいくつか追加されました。
iterateメソッド
まず、iterateメソッドです。このiterateメソッドは無限ストリームだけでなく、for文のように終了条件付きで記述することができます。
リスト2はJava 9とそれ以前で、1から9までの数を表示するときの例です。
// Java 9での記述例 Stream.iterate(1,i -> i < 10,i -> i + 1).forEach(System.out::println); // Java 9以前での記述例 Stream.iterate(1,i -> i + 1).limit(9).forEach(System.out::println);
ofNullableメソッド
ofNullableメソッドが新たに追加されました。このメソッドを使えばnull以外のデータを扱う際に、リスト3の記述が可能です。
// (1) Java 9での記述例 Stream.of(null,7,2,4,3,5,null,6).flatMap(i -> Stream.ofNullable(i)).forEach(System.out::println); // (2) Java 9以前での記述例 Stream.of(null,7,2,4,3,5,null,6).flatMap(i -> { if(i == null){ return Stream.empty(); } else{ return Stream.of(i); } }).forEach(System.out::println);
takeWhile/dropWhileメソッド
続いては、takeWhileメソッドとdropWhileメソッドです。
takeWhileは条件に一致している間のみ処理を実施し、条件に合わなくなると、それ以降は処理を行ないません。dropWhileは反対に、条件に一致する間のみ処理をせず、条件に一致しないデータが現れて以降、処理をする場合に利用できます。
例えば、1から9までの数値が連続するようなケースを想定したケースにおいて、takeWhileとdropWhileの振る舞いを示す例がリスト4です。この例ではイメージしやすいようにデータを少数にしていますが、無限ストリームなどですべてのデータを処理せずに済むので、効率よくデータを処理できます。
// (1) 1,2,3,4が出力される Stream.of(1,2,3,4,5,6,7,8,9,1,2,3).takeWhile( i -> { if( i > 0 && i < 5) { return true; } else{ return false; } }).forEach(System.out::println); // (2) 5,6,7,8,9,1,2,3が出力される Stream.of(1,2,3,4,5,6,7,8,9,1,2,3).dropWhile( i -> { if( i > 0 && i < 5) { return true; } else{ return false; } }).forEach(System.out::println);
(1)のtakeWhileでは、最初の1から4までのデータを出力しています。2回目に現れる1から3までの値は、その前に条件に一致しないデータである5が現れた時点で処理の対象にならないので出力されません。
また(2)では同じデータ、条件をdropWhileに置き換えたときの処理です。この場合は、条件に一致しない1から4までのデータではない5が現れた時点からが処理の対象になり、2回目に現れる1から3も処理の対象になります。
改善されたProcess API
Javaではネイティブのプロセスを扱うため、ProcessクラスやProcessBuilderクラスなどが存在します。ですが、これだけでは実現できないケースもあります。
例えば、自分でデーモン化するプログラムを作っていると、起動時に自分でPIDファイルを作成したいことがあります。しかし、自分自身のプロセスIDは簡単に取得できないので、JMXなどを利用するなど、ちょっとした工夫が必要でした。Java 9ではプロセスに関する情報を取得するためのProcessHandleインターフェースが追加され、リスト5のようにプロセスに関する情報が取得できます。
// (1) 自分自身のプロセスを取得 ProcessHandle handle = ProcessHandle.current(); long pid = handle.pid(); // (2) 親プロセスを取得 ProcessHandle parent = handle.parent().get(); // (3) プロセスIDから取得する ProcessHandle handle2 = ProcessHandle.of(pid).get(); // (4) プロセスに関する情報を取得する ProcessHandle.Info info = handle2.info(); String command = info.command().get(); String user = info.user().get();
自分自身のプロセスを取得するには(1)の通りProcessHandleのcurrentメソッドで取得します。親プロセスを取得するには(2)のようにparentメソッドが用意されています。これ以外にもプロセス終了の指示や、プロセスが生存しているかなどの確認も可能です。また、あらかじめプロセスIDがわかっている場合には、(3)の通りプロセスIDを指定して情報を取得することも可能です。
また、(4)のようにプロセスに関する情報を取得するためのProcessHandle.Infoインターフェースも追加されていて、以下の情報が取得できます。
- プロセスを起動したときの引数(argumentsメソッド)
- プロセスを実行したコマンド名(commandメソッド)
- プロセスを起動したときのコマンドライン(commandLineメソッド)
- プロセスの開始時間(startInstantメソッド)
- プロセスの合計cpu時間(totalCpuDurationメソッド)
- プロセスを起動したユーザ(userメソッド)
またProcessBuilderクラスでは、これまでstartメソッドを使って外部プログラムを実行していました。今回、startPipelineメソッドが追加され、パイプ(|)を用いたプロセスの処理が簡単に実行できるようになりました。
Javaに関するプロセスが現在いくつあるかを調べる処理について、パイプを使って実行した際の例がリスト6です。
// (1) プロセス一覧から結果から、"java"という文字を含む行数を取得する // $ps -ef | grep java | wc -l // (2) ProcessBuilderでのstartPipelineメソッドを使う場合 // (3) "ps -ef"を実行するプロセスを起動する処理 ProcessBuilder ps = new ProcessBuilder().command("ps","-ef"); // (4) "grep java"を実行するプロセスを起動する処理 ProcessBuilder grep = new ProcessBuilder().command("grep","java"); // (5) "wc -l"を実行するプロセスを起動する処理 ProcessBuilder wc = new ProcessBuilder().command("wc","-l"); try { // (6) それぞれのプロセスをパイプでつなげて実行する List<Process> processes = ProcessBuilder.startPipeline(List.of(ps,grep,wc)); // (7) 最後のプロセス(wc)を取得し、そのプロセスの終了を待つ Process p = processes.get(processes.size() - 1); int wait = p.waitFor(); // (8) wcプロセスの出力を表示する BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); String line; while((line = reader.readLine()) != null ) { System.out.println(line); } } catch(Exception ex){ }
(1)はUnix系のOSでパイプを使ってコマンドを実行したときの例です。この処理と同じようにパイプを使ってJavaで実行したのが(2)以降の処理になります。
(3)(4)(5)では各プロセスを起動するための準備をします。そして、(6)のようにstartPipelineメソッドを使ってそれらを起動します。(7)では最後のプロセスの終了を待ち、(8)ではその結果を標準出力に表示しています。
Javaのコードにすると記述するコードは増えてしまいますが、コマンドライン上での実行時と似た流れで処理を記述できます。