【Java】JDK 27 逆引き — LazyConstant・プリミティブ型パターン

JDK 27 逆引きを表したアイキャッチ画像

JDK 27で使えるLazyConstant・プリミティブ型パターン・構造化並行性の書き方を、OpenJDK 27での実行結果とあわせて逆引き形式でまとめます。プレビュー機能の有効化手順も分かります。

目次

この記事が解決する悩み

  • JDK 27 で何が使えるようになったか、動くコードで早く確かめたい
  • プレビュー機能を有効にする手順と、古い JDK で落ちる条件を先に知っておきたい
  • LazyConstant や構造化並行性の「書き方」だけを短く逆引きしたい

対象読者: Java を業務で使い、JDK 25 あたりまで追いかけている方。

前提知識: レコード・パターンマッチング・仮想スレッドの基本。プレビュー機能の経験は不要です。

本記事が扱う機能のうち、Lazy Constants(JEP 531)・Primitive Types in Patterns(JEP 532)・Structured Concurrency(JEP 533)・PEM Encodings(JEP 538)はプレビューです。仕様は今後変わる可能性があります。根拠はJDK 27 の公式ページにあります。

結論

JDK 27 の逆引きで最初に覚えるのは、プレビューの有効化と3つの入口です。

# プレビューはコンパイルと実行の両方で有効にする
javac --release 27 --enable-preview Main.java
java --enable-preview Main
// 遅延初期化: 初回の get() だけが計算する
LazyConstant<String> value = LazyConstant.of(() -> "computed");

// プリミティブ型のパターン: default を型付きの case に置き換える
switch (status) { case int i when i >= 100 -> "大きい"; default -> "その他"; }

// 構造化並行性: サブタスクはスコープを抜ける前に必ず合流する
try (var scope = StructuredTaskScope.<String>open()) { }

以降は「〜するにはどう書くか」の形で、私の環境で実際に動かせたものだけを並べます。

JDK 27 の変更点(JEP 9本)

JEP 内容 状態
523 G1 GC を全環境の既定にする 確定
527 TLS 1.3 の耐量子ハイブリッド鍵交換 確定
531 Lazy Constants(遅延定数) プレビュー
532 プリミティブ型のパターン・instanceof・switch プレビュー
533 構造化並行性 プレビュー
534 コンパクトオブジェクトヘッダを既定にする 確定
536 JFR のプロセス内データ・リダクション 確定
537 Vector API インキュベータ
538 暗号オブジェクトの PEM エンコード プレビュー

GA は 2026 年 9 月 15 日です。LTS は JDK 25 のままなので、27 は検証用の feature release という位置づけになります。

遅延初期化の逆引き

LazyConstant でフィールドの初期化を後回しにするには

計算用のラムダを渡して final フィールドに置くだけです。

int[] calls = { 0 };
var lazy = java.lang.LazyConstant.of(() -> {
    calls[0]++;
    return "computed#" + calls[0];
});
System.out.println("LazyConstant 1回目 = " + lazy.get());
System.out.println("LazyConstant 2回目 = " + lazy.get() + " / ラムダの実行回数 = " + calls[0]);

// 実行結果
// LazyConstant 1回目 = computed#1
// LazyConstant 2回目 = computed#1 / ラムダの実行回数 = 1
  • 初回の get() だけがラムダを評価します。実測では、ラムダの中で数えた実行回数が2回目の get() の後も1のままでした
  • final フィールドに置けば、JVM が内容を真の定数として扱い定数畳み込みの対象になります
  • 計算結果として null は返せません(仕様)

遅延 List / Map / Set を作るには

要素ごとに独立して遅延させたいときは、コレクション側の ofLazy を使います。

List<String> lazyList = List.ofLazy(3, i -> "item" + (i * i));
System.out.println("List.ofLazy = " + lazyList);

Map<String, String> lazyMap = Map.ofLazy(Set.of("a", "b"), String::toUpperCase);
System.out.println("Map.ofLazy = " + lazyMap.get("a") + "," + lazyMap.get("b"));

Set<Option> lazySet = Set.ofLazy(EnumSet.allOf(Option.class), o -> o != Option.DRY_RUN);
System.out.println("Set.ofLazy contains(DRY_RUN)=" + lazySet.contains(Option.DRY_RUN)
        + " size=" + lazySet.size());

// 実行結果
// List.ofLazy = [item0, item1, item4]
// Map.ofLazy = A,B
// Set.ofLazy contains(DRY_RUN)=false size=2

実測の出力は List が [item0, item1, item4]、Map が A と B、Set がサイズ2で DRY_RUN を含みません。List はサイズが固定で、Map はキーが構築時に決まっている必要があります。

パターンマッチングの逆引き

プリミティブ型を switch のパターンで受けるには

default を型付きの case に置き換えると、値に名前を付けられます。

static String describe(int status) {
    return switch (status) {
        case 0 -> "ゼロ";
        case int i when i >= 100 -> "大きい(" + i + ")";
        case int i -> "その他(" + i + ")";
    };
}

System.out.println("switch(int) = " + describe(0) + " / " + describe(150) + " / " + describe(7));

// 実行結果
// switch(int) = ゼロ / 大きい(150) / その他(7)

実測では describe(0) が「ゼロ」、describe(150) が「大きい(150)」、describe(7) が「その他(7)」を返します。0・150・7 に対して「ゼロ」「大きい(150)」「その他(7)」が返ります。default を書かなくても case int i が残りを拾うので網羅的です。

instanceof と record パターンでプリミティブを絞り込むには

instanceof でもプリミティブ型のパターンが使えます。record パターンなら double から int への絞り込みも、値が合わなければ単に不一致になります。

record JsonNumber(double value) {}

Object boxed = 300;
if (boxed instanceof int i) {
    System.out.println("instanceof int = " + i);
}

Object json = new JsonNumber(30.0);
if (json instanceof JsonNumber(int age)) {
    System.out.println("record パターンで double -> int = " + age);
}

// 実行結果
// instanceof int = 300
// record パターンで double -> int = 30

実測では instanceof int = 300、record パターンによる絞り込みで 30 と出力されます。桁が足りない値はマッチしないだけなので、情報が落ちる危険のあるキャストを書かずに済みます。

構造化並行性の逆引き

サブタスクを並行に走らせて合流させるには

スコープを try-with-resources で開き、fork した Subtask を join の後に読むだけです。

try (var scope = StructuredTaskScope.<String>open()) {
    var user = scope.fork(() -> "user-7");
    var order = scope.fork(() -> "order-42");
    scope.join();
    System.out.println("StructuredTaskScope join = " + user.get() + " / " + order.get());
}

// 実行結果
// StructuredTaskScope join = user-7 / order-42

実測では user-7 と order-42 が返ります。Joiner を渡さない open() は、join で ExecutionException を投げません。

タイムアウト付きで join するには

Joiner と Configuration を渡す open() を使い、withTimeout で期限を切ります。

try (var scope = StructuredTaskScope.<String, List<String>, java.util.concurrent.ExecutionException>open(
        StructuredTaskScope.Joiner.allSuccessfulOrThrow(),
        cfg -> cfg.withTimeout(Duration.ofMillis(200)))) {
    scope.fork(() -> "fast");
    scope.fork(() -> "faster");
    List<String> results = scope.join();
    System.out.println("timeout 付き join = " + results);
}

// 実行結果
// timeout 付き join = [fast, faster]

実測では join() の戻り値が [fast, faster] になります。型引数が3個必要な点は、後述のつまずきポイントに書きました。

  • allSuccessfulOrThrow() は全成功を待って結果のリストを返し、どれかが失敗すると join が ExecutionException を投げます
  • JEP 533 で join の例外型が3つ目の型引数 R_X になったため、結果の型も宣言で決める必要があります
  • Joiner.awaitAll() は削除され、onTimeout() は timeout() に置き換わっています

ランタイムとセキュリティの逆引き

コンパクトオブジェクトヘッダが有効か確認するには

起動フラグの既定値をそのまま見るのが確実です。

java -XX:+PrintFlagsFinal -version | grep UseCompactObjectHeaders

# 実行結果(JDK 27 / JDK 25 の比較)
# JDK 27: bool UseCompactObjectHeaders = true  {product lp64_product} {default}
# JDK 25: bool UseCompactObjectHeaders = false {product lp64_product} {default}

オブジェクトヘッダが 96bit から 64bit になり、ヒープ使用量とデータ局所性が改善します。無効化したいときは -XX:-UseCompactObjectHeaders を付けます。

G1 が既定の GC か確認するには

同じくフラグ出力で確認できます。

java -XX:+PrintFlagsFinal -version | grep UseG1GC

# 実行結果
# bool UseG1GC = true {product} {ergonomic}

JDK 27 からは CPU 1基やヒープ 1792MB 未満のような小さい環境でも、Serial ではなく G1 が選ばれます。

TLS 1.3 で耐量子ハイブリッド鍵交換を使うには

何もしなくても有効です。既定の named group の先頭が耐量子ハイブリッドになっています。

String[] groups = SSLContext.getDefault().getSupportedSSLParameters().getNamedGroups();
System.out.println("TLS named groups = " + String.join(", ", groups)
        + " (" + groups.length + " 個)");

// 実行結果
// TLS named groups = X25519MLKEM768, x25519, secp256r1, secp384r1, secp521r1, x448, ffdhe2048, ffdhe3072, ffdhe4096 (9 個)

実測では X25519MLKEM768, x25519, secp256r1, secp384r1, secp521r1, x448, ffdhe2048, ffdhe3072, ffdhe4096 の9個でした。優先順位を変えたいときは SSLParameters.setNamedGroups() を使います。

公開鍵や証明書を PEM 文字列にするには

PEMEncoder に鍵や証明書をそのまま渡せます。

var kp = KeyPairGenerator.getInstance("RSA");
kp.initialize(2048);
var pair = kp.generateKeyPair();
AsymmetricKey pub = pair.getPublic();
String pem = PEMEncoder.of().encodeToString(pub);
System.out.println("PEM 1 行目 = " + pem.lines().findFirst().orElse("?"));

// 実行結果
// PEM 1 行目 = -----BEGIN PUBLIC KEY-----

実測の1行目は —–BEGIN PUBLIC KEY—– です。読み戻しは PEMDecoder、暗号化付きで書き出すなら withEncryption(char[]) を使います。

つまずきポイント

  • –enable-preview を付けずに起動すると LinkageError になります。 「Preview features are not enabled for Jdk27Recipes (class file version 71.65535). Try running with 「–enable-preview」」が出ます
  • プレビューでコンパイルしたクラスは JDK 26 以前では読めません。 JDK 25 で起動すると「this version of the Java Runtime only recognizes class file versions up to 69.0」で止まります。 class file version は 71.65535 です
  • open(Joiner, Configuration) は型引数が3個必要です。 StructuredTaskScope.<String>open(…) と書くと「型引数の数が不正です。3個必要です」でコンパイルが止まります
  • LazyConstant は null を計算結果として許しません。null を返しうる処理は Optional で包んでください(仕様)

検証環境

OS Windows 11 (build 10.0.26200)
CPU AMD Ryzen 9 PRO 8945HS w/ Radeon 780M Graphics
メモリ 28GB
言語/ツール OpenJDK 27 GA(openjdk-27_windows-x64_bin.zip)/ build 27+35-2325 / Oracle JDK 25.0.1(比較用・ホスト既存)
使用コマンド javac –release 27 –enable-preview / java –enable-preview / java -XX:+PrintFlagsFinal -version
測定日 2026-09-19

各セクションに載せているコードと実行結果は、すべてこの環境で実際に実行したものです。環境が異なると挙動が変わることがあります。

OpenJDK 27 で LazyConstant・ofLazy コレクション・プリミティブ型パターン・構造化並行性(タイムアウト付き join)・TLS の named group・PEM エンコードを実行した結果と、--enable-preview 無し・JDK 25 で起動したときのエラー、コンパクトオブジェクトヘッダと G1 の既定値の比較
OpenJDK 27 で LazyConstant・ofLazy コレクション・プリミティブ型パターン・構造化並行性(タイムアウト付き join)・TLS の named group・PEM エンコードを実行した結果と、–enable-preview 無し・JDK 25 で起動したときのエラー、コンパクトオブジェクトヘッダと G1 の既定値の比較

まとめ

やりたいこと 書き方 状態
フィールドを遅延初期化する LazyConstant.of(Supplier) プレビュー
コレクションの要素を遅延させる List.ofLazy / Map.ofLazy / Set.ofLazy プレビュー
プリミティブをパターンで受ける case int i when … プレビュー
サブタスクをまとめる StructuredTaskScope.open() + join() プレビュー
ヘッダを小さくする 既定で有効(-XX:-UseCompactObjectHeaders で無効化) 確定
耐量子の鍵交換を使う 既定で X25519MLKEM768 が先頭 確定
鍵を PEM にする PEMEncoder.of().encodeToString(…) プレビュー

確定分(523 / 527 / 534 / 536)はそのまま本番へ。プレビュー分は –enable-preview を付けた検証環境に留めるのが安全ですね。

【広告】【Value AI Writer byGMO】高品質なSEO記事をAIで自動生成。

参考

あわせて読みたい

【広告】このサイトはConoHa WINGで運営しています。安定した高速サーバーで快適にブログを書けています。いつもありがとう!!

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

CAPTCHA


目次