diff --git "a/source/_posts/2025/20250922a_Java25\343\203\252\343\203\252\343\203\274\343\202\271\350\250\230\345\277\265\343\203\226\343\203\255\343\202\260\351\200\243\350\274\211.md" "b/source/_posts/2025/20250922a_Java25\343\203\252\343\203\252\343\203\274\343\202\271\350\250\230\345\277\265\343\203\226\343\203\255\343\202\260\351\200\243\350\274\211.md" index 1b5b6e1fe9a..2d11717fff4 100644 --- "a/source/_posts/2025/20250922a_Java25\343\203\252\343\203\252\343\203\274\343\202\271\350\250\230\345\277\265\343\203\226\343\203\255\343\202\260\351\200\243\350\274\211.md" +++ "b/source/_posts/2025/20250922a_Java25\343\203\252\343\203\252\343\203\274\343\202\271\350\250\230\345\277\265\343\203\226\343\203\255\343\202\260\351\200\243\350\274\211.md" @@ -13,7 +13,7 @@ lede: "Java 25のリリースを記念して、フューチャー技術ブログ --- -Java 25のリリースを記念して、フューチャー技術ブログでブログリレーを開催します! +Java 25のリリースを記念して、Java 24 & 25 についてのフューチャー技術ブログでブログリレーを開催します! 社内のJavaエキスパートたちが日替わりで記事を公開していきます。最新のJavaの世界をぜひお楽しみください。 @@ -22,9 +22,8 @@ Java 25のリリースを記念して、フューチャー技術ブログでブ | 日付 | 執筆者 | タイトル | | :--- | :--- | :--- | | 9/22(月) | 前川 喜洋 | [JDK24](/articles/20250922b/)| -| 9/23(火) | - | (秋分の日) | | 9/24(水) | 武田 大輝 |[Java 24 & 25 連載: Java 24におけるパフォーマンス周りのアップデート](/articles/20250924a/) | -| 9/25(木) | 金澤 祐輔 | JDK 25 フレームワーク互換性ウォッチ | -| 9/26(金) | 岸本 卓也 | JDK 25の新機能を見てみる | +| 10/2(木) | 岸本 卓也 | [JDK 25の新機能を見てみる](/articles/20251002a/) | +| 10/x(x) | 金澤 祐輔 | JDK 25 フレームワーク互換性ウォッチ | フューチャーで最も利用されている言語、Javaを盛り上げていきましょう! diff --git "a/source/_posts/2025/20251002a_Java_24_&_25\357\274\232_\351\200\243\350\274\211_JDK25\343\201\256\343\202\242\343\203\203\343\203\227\343\203\207\343\203\274\343\203\210.md" "b/source/_posts/2025/20251002a_Java_24_&_25\357\274\232_\351\200\243\350\274\211_JDK25\343\201\256\343\202\242\343\203\203\343\203\227\343\203\207\343\203\274\343\203\210.md" new file mode 100644 index 00000000000..b22c446f1d4 --- /dev/null +++ "b/source/_posts/2025/20251002a_Java_24_&_25\357\274\232_\351\200\243\350\274\211_JDK25\343\201\256\343\202\242\343\203\203\343\203\227\343\203\207\343\203\274\343\203\210.md" @@ -0,0 +1,387 @@ +--- +title: "JDK25のアップデート" +date: 2025/10/02 00:00:00 +postid: a +tag: + - Java + - JEP +category: + - Programming +thumbnail: /images/2025/20251002a/thumbnail.jpg +author: 岸本卓也 +lede: "JDK 25 の変更点の内、プレビュー以外の言語機能に関する変更点を中心に内容を見ていきます。" +--- + + +# はじめに + +こんにちは、TIGの岸本卓也です。 [Java25リリース記念ブログ連載](/articles/20250922a/) シリーズの記事です。 + +本稿では以下の JDK 25 の変更点の内、プレビュー以外の言語機能に関する変更点を中心に内容を見ていきます。 + +| JEP | Title | Category | Sub category | +| --- | --------------------------------------------------------------------------------------------------- | -------------------- | ------------ | +| 470 | [PEM Encodings of Cryptographic Objects (Preview)](https://openjdk.org/jeps/470) | Preview & Incubating | Libraries | +| 502 | [Stable Values (Preview)](https://openjdk.org/jeps/502) | Preview & Incubating | Libraries | +| 503 | [Remove the 32-bit x86 Port](https://openjdk.org/jeps/503) | Removals | HotSpot JVM | +| 505 | [Structured Concurrency (Fifth Preview)](https://openjdk.org/jeps/505) | Preview & Incubating | Libraries | +| 506 | [Scoped Values](https://openjdk.org/jeps/506) | Additions | Libraries | +| 507 | [Primitive Types in Patterns, instanceof, and switch (Third Preview)](https://openjdk.org/jeps/507) | Preview & Incubating | Language | +| 508 | [Vector API (Tenth Incubator)](https://openjdk.org/jeps/508) | Preview & Incubating | Libraries | +| 509 | [JFR CPU-Time Profiling (Experimental)](https://openjdk.org/jeps/509) | Additions | HotSpot JVM | +| 510 | [Key Derivation Function API](https://openjdk.org/jeps/510) | Additions | Libraries | +| 511 | [Module Import Declarations](https://openjdk.org/jeps/511) | Additions | Language | +| 512 | [Compact Source Files and Instance Main Methods](https://openjdk.org/jeps/512) | Additions | Language | +| 513 | [Flexible Constructor Bodies](https://openjdk.org/jeps/513) | Additions | Language | +| 514 | [Ahead-of-Time Command-Line Ergonomics](https://openjdk.org/jeps/514) | Additions | HotSpot JVM | +| 515 | [Ahead-of-Time Method Profiling](https://openjdk.org/jeps/515) | Additions | HotSpot JVM | +| 518 | [JFR Cooperative Sampling](https://openjdk.org/jeps/518) | Additions | HotSpot JVM | +| 519 | [Compact Object Headers](https://openjdk.org/jeps/519) | Additions | HotSpot JVM | +| 520 | [JFR Method Timing & Tracing](https://openjdk.org/jeps/520) | Additions | HotSpot JVM | +| 521 | [Generational Shenandoah](https://openjdk.org/jeps/521) | Additions | HotSpot JVM | + +# 503: [Remove the 32-bit x86 Port](https://openjdk.org/jeps/503) + +JEP 449 (JDK 21) 辺りから 32-bit x86 サポートの廃止に向けた動きが進んできていました。 Linux 向けの 32-bit x86 関連ソースが残るのみとなっていましたが、32-bit x86 サポートのために発生していた足かせの排除やビルド・テストの簡素化のために JEP 503 にてソースが削除されました。 + +# 506: [Scoped Values](https://openjdk.org/jeps/506) + +JEP 429 (JDK 20) からプレビュー提供されていた Scoped Values が、プレビューを終えて正式リリースされました。本機能は主に [`ScopedValue`](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/ScopedValue.html) クラスにより提供され、次のような場合に役立ちます。 + +メソッドにデータを渡す方法として、メソッド引数で渡す方法があります。メソッド引数は単純で分かりやすい方法ですが、メソッド内でさらに別のメソッドを呼び出して…といった構成で実際にデータを使うのが深い階層のメソッドである場合、各メソッド呼び出しに引数を定義してデータをバケツリレーする必要があります。メソッド引数で渡す方法は、中間のメソッドではそのデータを使わないとしてもバケツリレーのために引数を定義する必要があるという点が課題です。 + +これを解決する方法として `ThreadLocal` がよく使われます。例えば、 + +1. Framework がコンテキスト情報を生成する +2. そのコンテキスト情報のもとで Framework が Application を実行する +3. Application が Framework の機能を呼び出す +4. 呼び出された Framework 機能がコンテキスト情報を使う + +といった構成において、コンテキスト情報を `ThreadLocal` で渡す実装例は以下です。 + +```java ThreadLocalExample.java +public class ThreadLocalExample { + void main() { + var fw = new Framework(new Application()); + fw.serve("Java"); + fw.serve("Future"); + } +} + +class Framework { + private static final ThreadLocal CONTEXT = new ThreadLocal<>(); + + private final Application application; + + public Framework(final Application application) { + this.application = application; + } + + public void serve(final String request) { + var context = new FrameworkContext(request); + CONTEXT.set(context); + application.handle(); + CONTEXT.remove(); + } + + public static void greet() { + IO.println("Hello %s!".formatted(CONTEXT.get().getName())); + } +} + +class FrameworkContext { + private String name; + + public FrameworkContext(final String name) { + this.name = name; + } + + public String getName() { + return this.name; + } +} + +class Application { + public void handle() { + Framework.greet(); + } +} +``` + +```console +C:\>java ThreadLocalExample.java +Hello Java! +Hello Future! +``` + +この実装は期待通り動きますが、 `ThreadLocal` の方法には以下の課題があります。 + +* 自由に変更できる: `ThreadLocal` にアクセスできるコードからは `set` メソッドにより自由に値が変更できます。いつ、どこから変更されるかわからない変数の管理は難しく、バグを生みがちです。 +* 無制限の存続期間: `ThreadLocal` に `set` された値は、スレッドが存続する間または `remove` メソッドが呼ばれるまで保持されます。 `remove` メソッド呼び出しは忘れがちなため不必要に長期間変数が生存する可能性があり、スレッドプールを使用する場合は意図せずリークして脆弱性やメモリリークに繋がります。 +* 継承が高コスト: 親スレッドのスレッドローカル変数を継承して子スレッドを作成できますが、全てのスレッドローカル変数に対する領域をを割り当てる必要があるため多くのメモリが必要となる可能性があります。 + +このような課題を解決するため、 `ThreadLocal` よりも汎用性を減らし用途を絞った新たな API が `ScopedValue` です。 `ScopedValue` により、同一スレッド内または子スレッドとの間でより安全かつ効率的にデータを共有できます。 + +先程の Framework-Application の例を `ScopedValue` に変更した実装例は以下です。 + +```java ScopedValueExample1.java +public class ScopedValueExample1 { + void main() { + var fw = new Framework(new Application()); + fw.serve("Java"); + fw.serve("Future"); + } +} + +class Framework { + // バインドされていない (i.e. 空の) scoped value は `newInstance` メソッドで作成する + private static final ScopedValue CONTEXT = ScopedValue.newInstance(); + + private final Application application; + + public Framework(final Application application) { + this.application = application; + } + + public void serve(final String request) { + var context = new FrameworkContext(request); + ScopedValue + // scoped value に値をバインドする (i.e. セットする) には `where` メソッドを使う + .where(CONTEXT, context) + // 値がバインドされた状態で実行したいコードは `run` メソッドで実行する + .run(() -> application.handle()); + } + + public static void greet() { + IO.println("Hello %s!".formatted(CONTEXT.get().getName())); + } +} + +// FrameworkContext と Application は先の例と同じため記載省略 +``` + +```console +C:\>java ScopedValueExample1.java +Hello Java! +Hello Future! +``` + +`ScopedValue` のスコープは以下のようにネストする (rebinding) こともできます。 + +```java ScopedValueExample2.java +private static final ScopedValue NAME = ScopedValue.newInstance(); + +void main() { + ScopedValue.where(NAME, "Java").run(this::doSomething1); +} + +private void doSomething1() { + greet("doSomething1"); + + // `ScopedValue` のネスト + ScopedValue.where(NAME, "Future").run(this::doSomething2); + + // ネストを抜けると scoped value は元の値に戻る + greet("doSomething1"); +} + +private void doSomething2() { + greet("doSomething2"); +} + +private void greet(final String from) { + IO.println("%s: Hello %s!".formatted(from, NAME.get())); +} +``` + +```console +C:\>java ScopedValueExample2.java +doSomething1: Hello Java! +doSomething2: Hello Future! +doSomething1: Hello Java! +``` + +`ThreadLocal` の場合は値を変更すると変更されたままです。 + +```java ThreadLocalExample2.java +private static final ThreadLocal NAME = new ThreadLocal<>(); + +void main(final String[] args) { + NAME.set("Java"); + doSomething1(); +} + +private void doSomething1() { + greet("doSomething1"); + + NAME.set("Future"); + doSomething2(); + + greet("doSomething1"); +} + +private void doSomething2() { + greet("doSomething2"); +} + +private void greet(final String from) { + IO.println("%s: Hello %s!".formatted(from, NAME.get())); +} +``` + +```console +C:\>java ThreadLocalExample2.java +doSomething1: Hello Java! +doSomething2: Hello Future! +doSomething1: Hello Future! +``` + +# 511: [Module Import Declarations](https://openjdk.org/jeps/511) + +モジュール単位でインポートできる仕組みです。これまでインポートの宣言方法は single-type-import と type-import-on-demand の2種類でした。それぞれの例は以下です。 + +single-type-import + +```java +import java.util.Map; +``` + +type-import-on-demand + +```java +import java.util.*; +``` + +新たに追加された module import によりモジュール単位でのインポートが可能になります。 + +```java +import module java.base; +``` + +同名クラスが存在する場合はどのクラスをインポートするか曖昧になります。そのような場合は single-type-import または type-import-on-demand のインポート宣言を追加 (shadowing) して曖昧さを解決します。 + +```java +import module java.base; // java.util.Date が含まれる +import module java.sql; // java.sql.Date が含まれる + +import java.util.*; // type-import-on-demand で曖昧さを解消する例 +``` + +なお、当社が公開している [Java コーディング規約](https://future-architect.github.io/coding-standards/documents/forJava/Java%E3%82%B3%E3%83%BC%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0%E8%A6%8F%E7%B4%84.html#%E3%82%A4%E3%83%B3%E3%83%9B%E3%82%9A%E3%83%BC%E3%83%88) もそうですが、インポート宣言は明確さを優先して single-type-import が好まれることが多いです。しかし、 JShell 使用時やお試し実装など明確さより便利さを優先できる場合には有用になりそうです。 + +# 512: [Compact Source Files and Instance Main Methods](https://openjdk.org/jeps/512) + +おまじない/ボイラープレート少なく Java プログラムを書き始められる仕組みです。プレビューでは simple source files と呼ばれていましたが compact source files という名称に変更して正式リリースされました。 + +これまでは例えば Hello, World! のコードは以下のように実装する必要がありました。 + +```java +public class HelloWorld { + public static void main(String[] args) { + System.out.println("Hello, World!"); + } +} +``` + +このコードには Hello, World! を出力するメインのコード以外に多くのコードが含まれているため、 Java 初学者を混乱させる可能性があります。 + +compact source files と instance main methods の仕組みを使うと、 Hello, World! のコードは以下にできます。 + +```java +// class 定義を省略できる +// main メソッドの修飾子を省略できる +// main メソッドの引数を省略できる +void main() { + // `java.lang` パッケージに新たに追加された IO クラスを使う + IO.println("Hello, World!"); +} +``` + +class 定義に囲まれていないフィールドやメソッドを含むソースを compact source file と呼び、それらのフィールドやメソッドをメンバーとする暗黙のクラスが定義されているように扱われます。暗黙に定義されるクラスは名前を持たないため、 `new` 演算子でインスタンス化することや static メソッドのメソッド参照はできません。しかし、後述の仕組みで作られるインスタンスを `this` で参照できます。 + +暗黙定義されるクラスは次の仕様になります。 + +* 無名パッケージに定義されたトップレベルの final クラス +* `Object` クラスを継承し、インターフェース実装は無し +* コンストラクタは引数無しのデフォルトコンストラクタのみ存在する + +main メソッドは次の仕組みで選択されます。 + +1. クラスに直接定義または継承で `String[]` を引数とする `main` メソッドがあればそのメソッドを選択する。 +2. クラスに直接定義または継承で引数無しの `main` メソッドがあればそのメソッドを選択する。 + +選択された `main` メソッドは次の仕組みで起動されます。 + +1. 選択されたメソッドが `static` ならそのメソッドを起動する。 +2. 選択されたメソッドがインスタンスメソッドなら、引数無しのコンストラクタを起動してインスタンスを作成してから選択されたメソッドを起動する。 + +この簡潔な実装方法は [506: Scoped Values](#506-scoped-values) の実装例でもしれっと使っていました。 + +また、 compact source files では `java.base` モジュールが自動でインポートされるため、以下のようによく使うクラスもインポート宣言無しに使えます。 + +```java +void main() { + var fruits = new String[] { "apple", "berry", "citrus" }; + var m = Stream.of(fruits).collect(Collectors.toMap( + s -> s.toUpperCase().substring(0,1), + Function.identity())); + m.forEach((k, v) -> IO.println(k + " " + v)); +} +``` + +# 513: [Flexible Constructor Bodies](https://openjdk.org/jeps/513) + +コンストラクタの処理でコンストラクタ呼び出し (`super(...)` や `this(...)`) より前に文 (statement) を置けるようになりました。これにより、例えば以下のガード節のような実装が可能になります。 + +```java +class A { + A(final int value) { + if (value < 0) { + throw new IllegalArgumentException(); + } + + super(value); + } +} +``` + +なお、コンストラクタ呼び出しより前では `this` や `super` などで作成中のインスタンスの参照はできず初期化のみできます。例えば以下はコンパイルエラーとなる実装です。 + +```java +class A { + int i; + A() { + // エラー: スーパータイプのコンストラクタの呼出し前はthisを参照できません + this.i++; + // エラー: スーパータイプのコンストラクタの呼出し前はiを参照できません + i++; + // エラー: スーパータイプのコンストラクタの呼出し前はthisを参照できません + this.hashCode(); + + super(); + + // コンストラクタ呼び出し後の参照はOK + this.i++; + this.hashCode(); + } +} +``` + +コンストラクタ呼び出しより前のインスタンスアクセスは初期化のみ可能です。 + +```java +class A { + int i; + A(final int value) { + // 初期化はできる + this.i = value; + super(); + } +} +``` + +# さいごに + +本稿では JDK 25 の言語機能系の変更点をピックアップして紹介しました。 + +今回始めて Java のリリース内容を調査しましたが、 [JDK 25 のページ](https://openjdk.org/projects/jdk/25/) やここからリンクされている各 JEP のページ、Oracle 社の [Updates ページ](https://docs.oracle.com/en/java/javase/25/language/java-language-changes-release.html) は説明やサンプルが多くとても参考になりました。 diff --git a/source/images/2025/20251002a/thumbnail.jpg b/source/images/2025/20251002a/thumbnail.jpg new file mode 100644 index 00000000000..14ebce9095f Binary files /dev/null and b/source/images/2025/20251002a/thumbnail.jpg differ