LoginSignup
26
19

Java 21の新機能をざっくりまとめてみた

Posted at

概要

Java 21 の LTS リリースが 2023/9/19 に迫っている。

に新機能が載っているので、ざっくり簡潔にまとめたいと思う。
※ただし、プレビュー機能およびインキュベータのJEPを除く。

JEP 431: Sequenced Collections

定義された順序でのコレクションを表すための下記インタフェースが追加された。

  • java.util.SequencedCollection
  • java.util.SequencedSet
  • java.util.SequencedMap

これらは下記の図の緑の位置に属していて、
スクリーンショット 2023-08-19 17.34.45.png

各インタフェースの定義は以下の通り、順序性を表すメソッドが定義されている。

SequencedCollectionインタフェースの定義
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();
}
SequencedSetインタフェースの定義
interface SequencedSet<E> extends Set<E>, SequencedCollection<E> {
    SequencedSet<E> reversed();    // covariant override
}
SequencedMapインタフェースの定義
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 の先頭と末尾を取得したい時、

Java 21 未満の場合
var firstItem = arrayList.iterator().next(); 
var lastItem = arrayList.get(arrayList.size() - 1);

と書いていたが、Java 21 からは

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 で導入され、例えば

Java 16 未満の場合
if (obj instanceof String) {
    String s = (String) obj;
    // sはStringとして利用可能
}

こう書かなければいけなかったところを

Java 16 以降の場合
if (obj instanceof String s) {
    // sはStringとして利用可能
}

こう書けるようになった。

使い方

今までは

Java 21 未満の場合
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);
    }
}

のようにしなければいけないところを

Java 21 以降の場合
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 のセクションを参照。

使い方

例えば、今までは

java 21 未満の場合
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;
}

こう書いていたところを

java 21 以降の場合
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 が同一になり、
共通キーの受け渡しに成功したことになる。

 
 
以上、終わり。

26
19
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
19