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

まとめ
| やりたいこと | 書き方 | 状態 |
|---|---|---|
| フィールドを遅延初期化する | 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で自動生成。![]()
参考
- JDK 27(OpenJDK 公式・機能一覧とスケジュール)
- JEP 531: Lazy Constants (Third Preview)
- JEP 532: Primitive Types in Patterns, instanceof, and switch (Fifth Preview)
- JEP 533: Structured Concurrency (Seventh Preview)
- JEP 534: Compact Object Headers by Default
- JEP 523: Make G1 the Default Garbage Collector in All Environments
- JEP 527: Post-Quantum Hybrid Key Exchange for TLS 1.3
- JEP 538: PEM Encodings of Cryptographic Objects (Third Preview)
- Java SE 27 API Documentation
あわせて読みたい
- 【C#】C# 14 逆引き — field・拡張メンバー・null条件代入【.NET 10】
- 【Kotlin】Kotlin 2.4.20 完全ガイド — SwiftエクスポートとWasm対応
- 【Rust】非同期クロージャ(async || {})の書き方・移行ガイド
- 【OSS】OpenCode 入門ガイド — OSS AIコーディングエージェントの使い方
【広告】このサイトはConoHa WINGで運営しています。安定した高速サーバーで快適にブログを書けています。いつもありがとう!!![]()

コメント