ダウンロード サンプルファイル (68.0 KB)
本連載ではこれまで、Java 9における比較的大きな変更点を紹介してきました。今回は、まだ紹介していない比較的細かな変更点を中心に紹介します。例を挙げると、「CollectionやStream処理の記述の改善」「Process API」「System.Loggerの追加」などです。これらを知らなくても代替手段があるため、それほど注意深く調べることはないかもしれません。しかし、知っていればより便利な使い方ができるはずです。
直感的な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のコードにすると記述するコードは増えてしまいますが、コマンドライン上での実行時と似た流れで処理を記述できます。
この記事は参考になりましたか?
- Java 9で「変わること」と、Javaのこれまで連載記事一覧
-
- 作成したライブラリの公開と実行イメージの作成
- 知っていると得する! Java 9になってより便利になった改善点
- リアクティブストリームとCompletableFutureの改善
- この記事の著者
-
WINGSプロジェクト 小林 昌弘(コバヤシ マサヒロ)
< WINGSプロジェクト について> 有限会社 WINGSプロジェクト が運営する、テクニカル執筆コミュニティ(代表 ...
※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です
-
山田 祥寛(ヤマダ ヨシヒロ)
静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるLaravel実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数。
※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です
この記事は参考になりましたか?
この記事をシェア
