概要
Java 21 の LTS リリースが 2023/9/19 に迫っている。
に新機能が載っているので、ざっくり簡潔にまとめたいと思う。
※ただし、プレビュー機能およびインキュベータのJEPを除く。
JEP 431: Sequenced Collections
定義された順序でのコレクションを表すための下記インタフェースが追加された。
java.util.SequencedCollection
java.util.SequencedSet
java.util.SequencedMap
各インタフェースの定義は以下の通り、順序性を表すメソッドが定義されている。
interface SequencedCollection<E> extends Collection<E> {
// new method
SequencedCollection<E> reversed();
// methods promoted from Deque
void addFirst(E);
void addLast(E);
E getFirst();
E getLast();
E removeFirst();
E removeLast();
}
interface SequencedSet<E> extends Set<E>, SequencedCollection<E> {
SequencedSet<E> reversed(); // covariant override
}
interface SequencedMap<K,V> extends Map<K,V> {
// new methods
SequencedMap<K,V> reversed();
SequencedSet<K> sequencedKeySet();
SequencedCollection<V> sequencedValues();
SequencedSet<Entry<K,V>> sequencedEntrySet();
V putFirst(K, V);
V putLast(K, V);
// methods promoted from NavigableMap
Entry<K, V> firstEntry();
Entry<K, V> lastEntry();
Entry<K, V> pollFirstEntry();
Entry<K, V> pollLastEntry();
}
使い方
例えば、今までは List
の先頭と末尾を取得したい時、
var firstItem = arrayList.iterator().next();
var lastItem = arrayList.get(arrayList.size() - 1);
と書いていたが、Java 21 からは
var firstItem = arrayList.getFirst();
var lastItem = arrayList.getLast();
と書くことができる。他にも、
ArrayList<Integer> arrayList = new ArrayList<>();
arrayList.add(1); // [1]
arrayList.addFirst(0); // [0, 1]
arrayList.addLast(2); // [0, 1, 2]
arrayList.getFirst(); // 0
arrayList.getLast(); // 2
arrayList.reversed(); // [2, 1, 0]
のように順序性の側面で便利に書けるようになった。
JEP 439: Generational ZGC
Z Garbage Collector(ZGC)を拡張して、アプリケーションのパフォーマンスを向上させた。
使い方
現段階ではデフォルトで Generational ZGC
を利用するようにはなっていない。
利用したい場合には、以下のようにコマンドラインオプションに指定することで利用できる。
java -XX:+UseZGC -XX:+ZGenerational ...
いずれは Generational ZGC
がデフォルトになる予定。
JEP 440: Record Patterns
レコードクラス(※ Java 16〜)に対して、パターンマッチングが利用可能になった。
パターンマッチングとは Java 16 で導入され、例えば
if (obj instanceof String) {
String s = (String) obj;
// sはStringとして利用可能
}
こう書かなければいけなかったところを
if (obj instanceof String s) {
// sはStringとして利用可能
}
こう書けるようになった。
使い方
今までは
record Point(int x, int y) {}
static void printSum(Object obj) {
if (obj instanceof Point p) {
int x = p.x();
int y = p.y();
System.out.println(x+y);
}
}
のようにしなければいけないところを
record Point(int x, int y) {}
static void printSum(Object obj) {
if (obj instanceof Point(int x, int y)) {
System.out.println(x+y);
}
}
こう書けるようになった。
このinstanceof
の部分のPoint(int x, int y)
のことをレコードパターン
という。
こう書くことで、そのスコープ内(= 今回で言うとif
内)で簡単にメソッド参照できるようになった。
JEP 441: Pattern Matching for switch
switch
式でパターンマッチングを利用できるようになった。
※パターンマッチングについては、上記 JEP 440 のセクションを参照。
使い方
例えば、今までは
static String formatter(Object obj) {
String formatted = "unknown";
if (obj instanceof Integer i) {
formatted = String.format("int %d", i);
} else if (obj instanceof Long l) {
formatted = String.format("long %d", l);
} else if (obj instanceof Double d) {
formatted = String.format("double %f", d);
} else if (obj instanceof String s) {
formatted = String.format("String %s", s);
}
return formatted;
}
こう書いていたところを
static String formatterPatternSwitch(Object obj) {
return switch (obj) {
case Integer i -> String.format("int %d", i);
case Long l -> String.format("long %d", l);
case Double d -> String.format("double %f", d);
case String s -> String.format("String %s", s);
default -> obj.toString();
};
}
こう書けるようになった。
他にも例が JEP 441 のページに書かれているので、気になる方は見てみてください。
JEP 444: Virtual Threads
仮想スレッドとは、高スループットなアプリケーションを実現するための軽量のスレッド。
Java 21 未満の場合、java.lang.Thread
のすべてのインスタンスはプラットフォームスレッド(= OSが提供するスレッド)になっている。プラットフォームスレッドは、基盤となる OS スレッド上で Java コードを実行し、コードの存続期間全体にわたって OS スレッドを独占する。プラットフォームスレッド数は OS スレッドの数に制限されてしまう。
仮想スレッドでは同じ OS スレッド上で Java コードを実行し、事実上共有できる。
プラットフォームスレッドは貴重な OS スレッドを独占するが、仮想スレッドは独占しない。
仮想スレッドの数は、OS スレッドの数よりもはるかに大きくすることができる。
なお、Java 21 ではプラットフォームスレッド or 仮想スレッドのどちらを利用するか選択できる。
使い方
例えば、仮想スレッドで1秒で終わるタスクを10,000個同時実行したい場合を考えると、
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
IntStream.range(0, 10_000).forEach(i -> {
executor.submit(() -> {
Thread.sleep(Duration.ofSeconds(1));
return i;
});
});
} // executor.close() is called implicitly, and waits
こう書ける。ここでは10,000個の仮想スレッドを生成し、その中でタスクを同時実行させている。
その他にも
Runnable runnable = () -> {
System.out.println("Hello, world!");
};
// 仮想スレッド生成
Thread vThread = Thread.ofVirtual().name("duke").unstarted(runnable);
// プラットフォームスレッド生成
Thread pThread = Thread.ofPlatform().name("duke").unstarted(runnable);
のようにもスレッド生成することができる。
詳しく知りたい方は JEP 444 のページを見てみてください。
注意点
先ほどのプログラムのタスクが単にスリープしているのではなく、1秒間の計算 (巨大な配列のソートなど) を実行している場合、仮想スレッドであろうとプラットフォームスレッドであろうと、スループットは向上しない。仮想スレッドはプラットフォームスレッドよりも高速にコードを実行するということではなく、プラットフォームスレッドのスレッド数制限によって待ちが発生してしまう場合に、仮想スレッドを使用することでスループットが大幅に向上する可能性がある。
JEP 449: Deprecate the Windows 32-bit x86 Port for Removal
Windows 32 ビット x86 版用のビルド時にエラーメッセージを出力するようにビルドシステムを更新する。将来的に Windows 32 ビット x86 版向けのリリースを廃止する予定。
JEP 451: Prepare to Disallow the Dynamic Loading of Agents
実行中の JVM に対してエージェントを動的ロードするときに警告を出力する。
将来的にデフォルトでエージェントの動的ロードを禁止する予定。
エージェントとは単純にjar
ファイルのことを指しており、エージェントの動的ロードとは、Java Instrumentation API
を使用して、Javaアプリケーションの実行中にバイトコードを変更することを指す。具体的な動的ロード方法はここでは割愛する。
JEP 452: Key Encapsulation Mechanism API
Key Encapsulation Mechanism (= KEM) APIが導入された。
KEM とは公開鍵暗号を用いて、送信者・受信者間で安全に共通キーを交換するためメカニズム。
その後のデータの暗号・復号はその共通キー(= 対称キーともいう)を使用して行う。
使い方
共通キーの受信者、送信者それぞれの目線に立ったロジックのサンプルは以下の通り。
// Receiver side
KeyPairGenerator g = KeyPairGenerator.getInstance("ABC");
KeyPair kp = g.generateKeyPair();
publishKey(kp.getPublic());
(1)受信者はABC
アルゴリズム(※例えです)を使ってキーペアを生成し、公開鍵を公開する。
// Sender side
KEM kemS = KEM.getInstance("ABC-KEM");
PublicKey pkR = retrieveKey();
ABCKEMParameterSpec specS = new ABCKEMParameterSpec(...);
KEM.Encapsulator e = kemS.newEncapsulator(pkR, null, specS);
KEM.Encapsulated enc = e.encapsulate();
SecretKey secS = enc.key();
sendBytes(enc.encapsulation());
sendBytes(enc.params());
(2)送信者はABC-KEM
アルゴリズム(※例えです)を使ってKEMインスタンスを生成。
(3)受信者の公開鍵を受け取ってAlgorithmParameterSpec
のサブクラスであるABCKEMParameterSpec
クラスと共にカプセル化して、カプセル化したデータとパラメータを受信者側に送る。
// Receiver side
byte[] em = receiveBytes();
byte[] params = receiveBytes();
KEM kemR = KEM.getInstance("ABC-KEM");
AlgorithmParameters algParams = AlgorithmParameters.getInstance("ABC-KEM");
algParams.init(params);
ABCKEMParameterSpec specR = algParams.getParameterSpec(ABCKEMParameterSpec.class);
KEM.Decapsulator d = kemR.newDecapsulator(kp.getPrivate(), specR);
SecretKey secR = d.decapsulate(em);
// secS and secR will be identical
(4)受信者はカプセル化されたデータとパラメータを受け取る。
(5)ABC-KEM
アルゴリズム(※カプセル化の時と一緒のもの)を使ってKEMインスタンスを生成。
(6)ABCKEMParameterSpec
クラスと(1)で作成した秘密鍵を使ってカプセル化を解除する。
(7)共通キーを取り出す。
こうすることで、送信者の secC
と 受信者の secR
が同一になり、
共通キーの受け渡しに成功したことになる。
以上、終わり。