Switchでのパターンマッチ
switch文を用いた条件分岐の拡張は、Java17までもプレビュー機能などでさまざまな拡張が行われてきました。例えば、Java17のプレビュー機能版ではリスト8のようなコードが記述できるようになりました。
protected void switchSample17(Object value){ if(value == null){ return; } switch (value){ case Integer i : if(i < 0) { System.out.println("less than 0"); } else{ System.out.println("more than 0"); } break; default: break; } }
これだけでも、既存のJavaのコードに比べると簡素にコードが記述できるようになっていますが、Java19ではリスト9のように書き換えることが可能です。これにより、比較となる対象とその該当処理がよりわかりやすく記述することが可能です。
protected void switchSample19(Object value){ switch (value){ // (1) case に nullを指定可能 case null : System.out.println("is null"); break; // (2) when による条件が指定可能 case Integer i when i < 0: System.out.println("less than 0"); break; case Integer i when i >= 0 : System.out.println("more than 0"); break; default: break; } }
(1)のようにnullも含めることができるようになりました。そして、今まではif文で分岐させていたような条件も(2)のようにwhenを使った表現が可能です。この記述方法は、リスト10のようにswitch文ではなく、switch式でも同様に記述が可能です。
return switch (value){ case null -> 0; case Integer i when i < 0 -> 1; case Integer i when i >= 0 -> 2; default -> -1; };
仮想スレッド
軽量版のスレッドが選択できるようになりました。これまでJavaでスレッドを作ると、JVMで決められたネイティブでのスレッドになります。そのため、スレッドを作るだけでも比較的負荷が高い処理になります。そのため、大量のスレッドを扱う場合にはスレッドプールなどのようにその負荷を軽減する方法などがとられてきました。
しかし、C10K問題に向き合わなければならない場合など、どうしてもその問題から避けられないケースもあり、Java側の対応だけではなくOS側でも調整が必要になるケースがありました。
今回では、そのような問題にも対応できる仮想スレッドが作成できるようになりました。この仮想スレッドは実際のOS上のスレッドではなく、JVM内で関係するスレッドです。これにより、軽量で大量のスレッド処理が行いやすくなりました。リスト11は仮想スレッドを作成する場合の簡単な実装例です。
// (1) 仮想スレッド用のFactoryクラスを作成 ThreadFactory factory = Thread.ofVirtual().factory(); // (2) 従来のスレッド用のFactoryクラスの場合 //ThreadFactory factory = Thread.ofPlatform().factory(); // (3) スレッドを作成する Thread t = factory.newThread(new Runnable() { @Override public void run() { System.out.println("new thread"); } }); t.start();
(1)で仮想スレッドを作成するためのFactoryクラスを作成します。既存のスレッド用のAPIでもThreadFactoryを引数にもつメソッドが追加されています。従来のスレッドを作成する場合には(2)のようにThreadFactoryを作成します。また、ThreadFactoryが引数にない従来のメソッドを利用した場合でも従来のスレッドが作成されます。あとは、(3)のようにスレッドを作成し、実行できます。
実際の動作の影響は、このような簡単な処理では違いがよくわかりません。しかし、リスト12のようなコードを実行した場合、その違いが生じます。
// (1) 仮想スレッドを用いた場合の実行 try( var executor = Executors.newVirtualThreadPerTaskExecutor(); ){ // (2) 大量のスレッドを実行する IntStream.range(0,10000).forEach(i ->{ executor.submit(() ->{ System.out.println("i is " + i); Thread.sleep(5000); return i; }); }); } // (3) 通常のスレッドを用いた場合の実行 ThreadFactory factory = Thread.ofPlatform().factory(); try( var executor = Executors.newThreadPerTaskExecutor(factory); ){ IntStream.range(0,1000).forEach(i ->{ executor.submit(() ->{ System.out.println("i is " + i); Thread.sleep(5000); return i; }); }); }
(1)は仮想スレッドを用いた場合のコードです。(2)で5秒ほどかかる処理を同時に10000個実行しています。同様の処理を通常のスレッドを用いて行ったものが、(3)のコードです。こちらは、筆者の環境では実行が終了できず、エラーになってしまいました。
もちろん、実行する環境によって上限は違いますが、このようにリソースを大量に消費するコードの場合には、仮想スレッドと通常のスレッドの違いがわかると思います。
Finalizeメソッドの無効オプションの追加
Java9からすでにfinalizeメソッドを利用したコードを記述することは推奨されていませんが、まだ、利用されているコードがあります。ただし、今後のバージョンではデフォルトで無効や削除が予定されています。
そこで、実行時に--finalization=disabledを指定することで、ファイナライザ機能を無効にすることができます。このオプションをつけて問題なく動くようにすることで、今後のJavaのバージョンアップの際にも思わぬ不具合をおこすようなことがないように準備できます。
まとめ
Java18とJava19はJava17までの変更に比べ、どちらかと言えば、これまでの機能や環境の整備といえる変更と筆者は感じました。次のLTSバージョンでも大きく変わらず同様の流れになると思います。Javaを中心に利用している筆者としては少々寂しくもありますが、より成熟した言語として利用されているということでもあり、大きな変更がないことはメリットでもあります。
一方で、クラウド環境との親和性などのようなJVM周りについてはまだまだ、発展する余地があります。今後はそのような機能が拡充され、Javaがより発展することを期待したいと思います。