Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
title: "Java25リリース記念ブログ連載"
date: 2025/09/22 00:00:00
postid: a
tag:
- インデックス
- Java
category:
- Programming
thumbnail: /images/2025/20250922a/thumbnail.jpg
author: 真野隼記
lede: "Java 25のリリースを記念して、フューチャー技術ブログでブログリレーを開催します!社内のJavaエキスパートたちが日替わりで記事を公開していきます。最新のJavaの世界をぜひお楽しみください。公開スケジュール"
---
<img src="/images/2025/20250922a/top.jpg" alt="" width="600" height="395">

Java 25のリリースを記念して、フューチャー技術ブログでブログリレーを開催します!

社内のJavaエキスパートたちが日替わりで記事を公開していきます。最新のJavaの世界をぜひお楽しみください。

**公開スケジュール**

| 日付 | 執筆者 | タイトル |
| :--- | :--- | :--- |
| 9/22(月) | 前川 喜洋 | [JDK24](/articles/20250922b/)|
| 9/23(火) | - | (秋分の日) |
| 9/24(水) | 武田 大輝 |Java 24 & 25 連載: Java 24におけるパフォーマンス周りのアップデート |
| 9/25(木) | 金澤 祐輔 | JDK 25 フレームワーク互換性ウォッチ |
| 9/26(金) | 岸本 卓也 | (タイトル未定) |

フューチャーで最も利用されている言語、Javaを盛り上げていきましょう!
263 changes: 263 additions & 0 deletions source/_posts/2025/20250922b_JDK24.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
---
title: "JDK24"
date: 2025/09/22 00:00:01
postid: b
tag:
- Java
category:
- Programming
thumbnail: /images/2025/20250922b/thumbnail.jpg
author: 前川喜洋
lede: "JDK 25のリリースを受けてバージョン24,25の変更点を紹介する連載企画の1本目の記事です。今回はJDK 24でのアップデート内容から以下についてピックアップしてご紹介します。"
---

<img src="/images/2025/20250922b/thumbnail.jpg" alt="" width="600" height="395">

## はじめに

コアテクノロジーグループの前川です。

[JDK 25のリリース連載](/articles/20250922a/) 1本目の記事です。

今回はJDK 24でのアップデート内容から以下についてピックアップしてご紹介します。

* API系
* [JEP 484: Class-File API (クラスファイルの解析、生成、変換を行うための標準API)](https://openjdk.org/jeps/484)
* [JEP 485: Stream Gatherers (Streamでより柔軟な中間操作を可能にするgatherメソッド)](https://openjdk.org/jeps/485)
* [JEP 478: Key Derivation Function API (鍵導出関数のためのAPI)](https://openjdk.org/jeps/478)
* セキュリティ系
* [JEP 472: Prepare to Restrict the Use of JNI (JNIの安全でない使用を制限)](https://openjdk.org/jeps/472)
* [JEP 486: Permanently Disable the Security Manager (セキュリティマネージャを恒久的に無効化)](https://openjdk.org/jeps/486)
* [JEP 496: Quantum-Resistant Module-Lattice-Based Key Encapsulation Mechanism (量子コンピュータによる攻撃に耐性)](https://openjdk.org/jeps/496)
* [JEP 497: Quantum-Resistant Module-Lattice-Based Digital Signature Algorithm (量子コンピュータによる攻撃に耐性)](https://openjdk.org/jeps/497)
* [JEP 498: Warn upon Use of Memory-Access Methods in sun.misc.Unsafe (sun.misc.Unsafeのメモリ操作メソッド使用時に警告)](https://openjdk.org/jeps/498)

## JEP 484: Class-File API (クラスファイルの解析、生成、変換を行うための標準API)

Java バイトコードを読み込んでクラスファイルの解析、生成、変換を行うための機能を公式に提供するためのライブラリとしては、ASM や BCEL などが広く使われていますが、これらは JDK の一部ではなく、外部ライブラリとしてプロジェクトに組み込む必要があります。JEP 484 では、こうした外部ライブラリに依存せずに、JDK 標準でクラスファイルの解析、生成、変換を行うための API を提供します。 JDK 22から2回のプレビューを経て正式リリースとなりました。

以下は `ClassBuilder` を使用して既存のクラスのメソッド冒頭に `System.out.println` を仕込んで新クラスのclassファイルを作成するサンプルです。

```java ClassRenamer.java
import java.lang.classfile.*;
import java.lang.constant.*;
import java.nio.file.*;
import java.io.IOException;

public class ClassRenamer {

public static void main(String[] args) throws IOException {
Path sourcePath = Path.of("MyTargetClass.class");
String newClassName = "MyTargetClass2";
Path targetPath = Path.of(newClassName + ".class");

// 元のクラスファイルを解析
byte[] classBytes = Files.readAllBytes(sourcePath);
ClassModel originalModel = ClassFile.of().parse(classBytes);

// 新しいクラス名でクラスビルダーを開始し、元のクラスの全要素を新しいビルダーにコピーする
byte[] newClassBytes = ClassFile.of().build(ClassDesc.of(newClassName), builder -> {
for (ClassElement element : originalModel) {
// 要素がメソッドの場合、コードを変換して追加
if (element instanceof MethodModel method) {
// withMethod で新しいメソッドを構築
builder.withMethod(method.methodName(), method.methodType(), method.flags().flagsMask(), methodBuilder -> {
// 元のメソッドの属性(アノテーションなど)をコピー
for (MethodElement me : method) {
if (!(me instanceof CodeModel)) {
methodBuilder.with(me);
}
}

boolean[] inserted = {false};
methodBuilder.transformCode(
method.code().orElseThrow(),
(codeBuilder, codeElement) -> {
// フラグが false の場合(=初回実行時)のみ挿入
if (!inserted[0]) {
insertPrintStatement(codeBuilder, ">> Entering method: " + method.methodName().stringValue());
inserted[0] = true;
}
codeBuilder.with(codeElement);
});
});
} else {
builder.with(element);
}
}
});
// 変換後のバイトコードを新しいファイルに書き出す
Files.write(targetPath, newClassBytes);

System.out.println("クラス名を '" + newClassName + "' に変更し、'" + targetPath + "' に保存しました。");
}

private static final ClassDesc SYSTEM = ClassDesc.of("java.lang.System");
private static final ClassDesc PRINT_STREAM = ClassDesc.of("java.io.PrintStream");
private static final MethodTypeDesc PRINTLN_MTD = MethodTypeDesc.of(ConstantDescs.CD_void, ConstantDescs.CD_String);

private static void insertPrintStatement(CodeBuilder codeBuilder, String message) {
codeBuilder.getstatic(SYSTEM, "out", PRINT_STREAM)
.ldc(message)
.invokevirtual(PRINT_STREAM, "println", PRINTLN_MTD);
}
}
```

```java MyTargetClass.java
public class MyTargetClass {
public static void main(String[] args) {
System.out.println("Hello!");
}
}
```

```bash
$ javac *.java
$ java -cp . ClassRenamer
クラス名を 'MyTargetClass2' に変更し、'MyTargetClass2.class' に保存しました。
$ java -cp . MyTargetClass2
>> Entering method: main
Hello!
```

考え方としてはASMに近いですが記法が異なっていて、ASMがビジターパターンなのに対してこちらはビルダーの多層構造になっており、 OpenRewrite 等でサクッと移行というわけにはいかなさそうです。

## JEP 485: Stream Gatherers (Streamでより柔軟な中間操作を可能にするgatherメソッド)

Streamパイプラインの中に、自由度の高いカスタム中間操作を組み込むことを可能にします。これにより、開発者はこれまで以上に柔軟かつ直観的にデータ処理を記述できるようになります。

`Collector` で頑張ろうとすると一旦「Stream脳」から離れないといけなかったのがかなり軽減されます。 `Integrator` の `state` 、遂に来たかという感じですね。

```java
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Gatherer;
import java.util.stream.Gatherers;
import java.util.stream.Stream;

public class StreamSample {
public static void main(String[] args) {
// 1から9までの数値を3つずつのリストにまとめる
List<List<Integer>> result = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9)
.gather(Gatherers.windowFixed(3))
.toList();

// 実行結果: [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
System.out.println(result);

// 1から5までの数値を、3つずつのスライディングウィンドウでまとめる
List<List<Integer>> result2 = Stream.of(1, 2, 3, 4, 5)
.gather(Gatherers.windowSliding(3))
.toList();

// 実行結果: [[1, 2, 3], [2, 3, 4], [3, 4, 5]]
System.out.println(result2);

// 数値の累積和を計算する
List<Integer> result3 = Stream.of(1, 2, 3, 4, 5)
.gather(Gatherers.scan(() -> 0, (sum, i) -> sum + i))
.toList();

// 実行結果: [1, 3, 6, 10, 15]
System.out.println(result3);

// カスタムGathererを使用
List<List<String>> result4 = Stream.of("a", "a", "b", "c", "c", "c", "b", "a")
.gather(groupConsecutive())
.toList();

// 実行結果: [["a", "a"], ["b"], ["c", "c", "c"], ["b"], ["a"]]
System.out.println(result4);
}

/** Stream内の連続する同じ要素をリストにグループ化するGathererを作成 */
public static <T> Gatherer<T, ?, List<T>> groupConsecutive() {
return Gatherer.ofSequential(
// 1. Initializer: 中間状態を保持するコンテナを準備する
ArrayList::new,

// 2. Integrator: 各要素を処理する
(state, element, downstream) -> {
// stateが空でなく、最後の要素が現在の要素と違う場合、これまでのstateをリストとして下流に流し、stateをクリアする
if (!state.isEmpty() && !state.getLast().equals(element)) {
downstream.push(List.copyOf(state));
state.clear();
}
// 現在の要素をstateに追加する
state.add(element);
return true;
},

// 3. Finisher: 最後の要素グループを処理する
(state, downstream) -> {
// Streamの最後に残った要素がstateにあれば、それを下流に流す
if (!state.isEmpty()) {
downstream.push(List.copyOf(state));
}
});
}
}
```

## JEP 478: Key Derivation Function API (鍵導出関数のためのAPI)

ポスト量子暗号(PQC)対応の一環。

鍵導出関数(KDF)をひとことで言うと、 **「鍵の"おおもと"(マスターキーやパスワード)から、用途に合わせて安全な"子鍵"を複数生成するための仕組み」** です。これまでJavaには鍵導出を行う為の統一的な標準APIが有りませんでした。この状況を改善し、標準化と相互運用性を高めてよりモダンで堅牢なアルゴリズムの利用を促進する事を目的として導入されたのが `javax.crypto.KDF` クラスです。

例はJEPのページからそのまま抜粋しますが以下のコードにより `javax.crypto.SecretKey` オブジェクトを生成できます。 `SecretKey` オブジェクトは従来と同様に `Cipher` クラスなどで利用できます。

```java
// Create a KDF object for the specified algorithm
KDF hkdf = KDF.getInstance("HKDF-SHA256");

// Create an ExtractExpand parameter specification
AlgorithmParameterSpec params =
HKDFParameterSpec.ofExtract()
.addIKM(initialKeyMaterial)
.addSalt(salt).thenExpand(info, 32);

// Derive a 32-byte AES key
SecretKey key = hkdf.deriveKey("AES", params);

// Additional deriveKey calls can be made with the same KDF object
```

バージョン24時点ではPreviewなので、コンパイル時と実行時に以下のオプションを指定する必要があります。

```
$ javac --release 24 --enable-preview Foo.java
$ java --enable-preview Foo

# または

$ java --enable-preview Foo.java

# または

$ jshell --enable-preview
```

## JEP 472: Prepare to Restrict the Use of JNI (JNIの安全でない使用を制限)

このJEPは、Java Native Interface の安全でない使用を将来的に制限するための準備です。ネイティブコードがJVMの整合性を損なう可能性のあるJNI関数を呼び出した際に、デフォルトで警告が表示されるようになりました。

## JEP 486: Permanently Disable the Security Manager (セキュリティマネージャを恒久的に無効化)

Java 17で非推奨となっていたセキュリティマネージャが、このバージョンでデフォルトで無効化されました。まだ完全な削除ではなく、コマンドラインオプションで有効化する事が可能です。

## JEP 496 & 497: Quantum-Resistant (量子コンピュータによる攻撃に耐性)

これら2つのJEPは、将来の量子コンピュータによる攻撃に耐えうる暗号技術を導入するものです。JEP 496では暗号通信のための鍵カプセル化メカニズム、JEP 497ではデジタル署名アルゴリズムが実装されました。

## JEP 498: Warn upon Use of Memory-Access Methods in sun.misc.Unsafe (sun.misc.Unsafeのメモリ操作メソッド使用時に警告)

`sun.misc.Unsafe` クラス内の特定のメモリ操作メソッドが使用された際に、警告が発せられるようになりました。これらのメソッドはJVMを不安定にするリスクがあるため、開発者には公式にサポートされている安全なAPIへの移行が推奨されています。

## おわりに

「ポスト量子」の世界がいよいよ現実味を増してきましたね。

次回は引続きJDK 24のご紹介が続きます。

Binary file added source/images/2025/20250922a/thumbnail.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file.
Binary file added source/images/2025/20250922a/top.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file.
Binary file added source/images/2025/20250922b/thumbnail.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Empty file.
Binary file added source/images/2025/20250922b/top.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading