2025/10/11

JEPで語るJava 25 その2

このエントリーをはてなブックマークに追加

前回は、Java 25のJEPのうち初心者向け機能と小さいオブジェクトを扱う機能を紹介しました。今回はその続きです。

まずはProject Leyden関連のJEPです。

 

Project Leyden

Project Leydenは、Javaの起動時間を短縮することを目標にしたプロジェクトです。

さくらばは、てっきりGraalのネイティブイメージを取り込むだけなのかと思っていたのですが、そうではなく、様々な手段を用いて起動時間とピークパフォーマンスに達するまでの時間を短縮するための機能を提供するようです。

Java 24ではJEP 483が導入されましたが、Java 25はこの続きになるJEPが2つ導入されました。

 

JEP 514: Ahead-of-Time Command-Line Ergonomics

JEP 483はクラスロード、クラスの解析、クラス間のリンク、static初期化を行った後のイメージをキャッシュとして保存して、起動時にそれを読み込むことによって起動時間を短縮させます。

このキャッシュ(AOTキャッシュと呼ばれます)を作るには、トレーニング実行を行って、その後キャッシュ作成するという2段階が必要でした。

しかし、起動時オプションを変えて、2回実行するというのはちょっとめんどう...

ということで、JEP 514は1度の実行でトレーニング実行とAOTキャッシュ作成を行ってしまいましょうというものです。

AOTキャッシュを作成するには以下のように実行します。

 

  • AOTキャッシュ作成
    $ java -XX:AOTCacheOutput=app.aot -cp app.jar com.example.App ...

AOTキャッシュを使って実行するのは、JEP 483と同じです。

  • 本番実行
    $ java -XX:AOTCache=app.aot -cp app.jar com.example.App ...

 

もちろん、JEP 483の2段階でAOTキャッシュを作成することも可能です。

 

JEP 515: Ahead-of-Time Method Profiling

HotSpot VMはアプリケーションを実行しながら、その片手間にプロファイリングもやっています。プロファイルイングの結果から、実行中にメソッドをネイティブコードにコンパイルするなどの最適化を行っています。

逆にいうと、ピークパフォーマンスに達するまでに時間がかかるということです。

Leydenで起動時間を短縮するのが必要なユースケースでは、なおさらピークパフォーマンスに達する時間が問題になりそうです。

そこで、JEP 515では、AOTキャッシュを作成するトレーニング実行中に作成したプロファイリングの解析結果をAOTキャッシュに含めるようになりました。

AOTキャッシュを使って実行すると、プロファイリングを行わずにすぐにネイティブコンパイルなどの最適化を行うことができるので、ピークパフォーマンスに到達する時間を大幅に短縮することができます。

 

Project Leydenでは、この後、JEP 516: Ahead-of-Time Object Caching with Any GCやまだドラフトですがJEP draft: Ahead-of-Time Code Compilationなどが控えています。

 

これらのProject Leydenの機能に関して、9月のJJUGナイトセミナーでOracleのじゅくちょー阪田さんが解説してくれたのですが、その資料がまだ公開されていない...

もし、LeydenのAOTキャッシュに興味があるのであれば、資料が公開されるのを待ちましょう!

 

安全性、セキュリティ

JEP 510の鍵導出関数はとりあえずおいておいて、サポートできなくなったポーティングは削除しましょうというのがJEP 503です。

 

JEP 503: Remove the 32-bit x86 Port

32bit版のWindowsのサポートが今月終了するということで、Java 24で32bit版のWindowsのポーティングが削除されました。また、x86のポーティングも削除予定になっていました。

そして、Java 25でx86のポーティングが削除されました。

サポートされないOSに対しては安全性の問題もあるので、削除はしかたないですね。

 

モニタリング

OpenJDKでのモニタリングとプロファイリングといえば、JDK Flight Recorder (JFR)ですね。

Java 25では、このJFR関連のJEPが3つあります。1つはExperimentalなので後述することにして、残りの2つについて簡単に紹介します。

とはいうものの、1つは内部実装の話なので、機能という感じではないですけど。

 

JEP 518: JFR Cooperative Sampling

非同期にスレッドのスタックトレースをサンプリングする部分の実装を見直して、安定性を高めたというJEP。

使い方の変更はないので、Java 25のJFRであれば安定してスタックトレースを取得できるようになるはずです。

 

JEP 520: JFR Method Timing & Tracing

メソッドがコールされたタイミングや回数などをサンプリングではなく、実測で調べるためのJFRのイベントが追加されました。

追加されたイベントはjdk.MethodTimeingとjdk.MethodTraceの2種類です。

これらのイベントを使用することで、メソッドがコールされたタイミングとメソッドの処理時間、そしてスタックトレースを取得することができます。

 

その他

Standard JEPの残った2つは、Project Loom関連のScoped Valueと、GCのShenandoah GCに関連したJEPです。

 

JEP 506: Scoped Values

ThreadLocalクラスはミュータブルでいろいろと問題のあるクラスですが、それをある程度置き換えることができるのがScopedValueクラスです。

すべてではないのはScopedValueクラスがイミュータブルなクラスなので、ThreadLocalクラスでsetメソッドを使っているようなケースは置き換えられないからです。

 

使い方は簡単で、ScopedValueインスタンスをnewInstance()メソッドで作成しておいて、そこにstaticメソッドのwhere()メソッドで保持する値をバインドさせます。

where()メソッドの返り値の型はScopedValue.Carrierクラスです。このScopedValue.Carrierクラスのrun()メソッド、もしくはcall()メソッドで実行するタスク内だけでScopedValueオブジェクトにバインドしたデータを使用できます。

つまり、run()メソッドとcall()メソッドで指定する関数だけがスコープになるわけです。

 

たとえば、時間を共有したい場合を考えてみます。

// スコープで共有するScopedValueオブジェクト
final ScopedValue<LocalDateTime> TIME = ScopedValue.newInstance();

void task1() {
  // get()メソッドで共有したデータを取得
  System.out.println("Task1: " + TIME.get());
}

void task2() {
  // get()メソッドで共有したデータを取得
  System.out.println("Task2: " + TIME.get());
}

void main() {
  Runnable r = () -> {
    // whereメソッドでTIMEに現在時刻をバインド
    // runメソッドで指定するラムダ式の中だけでバインドしたデータを共有
    ScopedValue.where(TIME, LocalDateTime.now())
               .run(() -> {
                  task1();
                  task2();
                });

  };

  try (var executor = Executors.newCachedThreadPool()) {
    executor.submit(r);
  }
}

 

とはいうものの、ThreadLocalクラスやScopedValueクラスを使ったデータの共有はできるだけ避ける方がよいです。共有したデータのせいでスケールしないことも多いですし、バグも発生しやすいです。

ScopedValueクラスを安易に使うよりは、もう一度設計を見直すことの方が賢明だと思います。

たとえば、上のコードでもtask1()メソッドとtask2()メソッドの引数として時間を渡した方が分かりやすいですよね。共有せずに済むのであれば、共有しない設計にすべきです。

 

JEP 521: Generational Shenandoah

Red Hatが進めていたShenandoah GCは、以前のZGCのように世代別GCではなかったのですが、ZGCの後を追うように世代別GCになりました。

Shenandoahで世代別モードにするには、まず実行時オプションの-XX:+UseShenandoahGCでGCにShenandoahを指定します。そして、同じく実行時オプションで-XX:ShenandoahGCMode=generationalを指定します。

 

Preview

ここからはお試し機能。まずは、Preview JEPからです。

Preview JEPは4種類。JEP 507はJava 25でStandardになると予想していたんですけど、Previewでしたね。残念。

 

JEP 470: PEM Encodings of Cryptographic Objects (Preview)

1つ目のPreview JEPは鍵導出関数に関するAPIです。

 

JEP 502: Stable Values (Preview)

StableValueクラスはfinalなフィールドに対して遅延初期化を行うためのクラスです。

それにしても、java.langパッケージにScopedValueクラスとStableValueクラスという似たようなクラスができると混乱するよなぁ... と思っていたら、Java 26がターゲットのJEP 526ではLazyConstantクラスに名前が変わりました!

LazyConstantクラスに関してはJJUG CCC 2025 Fallでプレゼンすることになったので、その時に詳しく解説します。

 

JEP 505: Structured Concurrency (Fifth Preview)

Structured ConcurrencyはなかなかStandard JEPになりませんが、複数の非同期実行のタスクの結果をまとめることなどに使用するAPIです。

Java 25で5回目のPreviewですが、APIに変更があったためJava 26でもう一度Previewということになっています(JEP 525)。

Java 24まではStructuredTaskScopeクラスのインスタンシエーションにはnewを使用していたのですが、Java 25でファクトリーメソッドのopen()メソッドが導入されています。

また、全部成功したらとか、1つでも失敗したらなどの条件を表すために、Java 24まではStructuredTaskScopeクラスのサブクラスで表していましたが、JEP 505ではStructuredTaskScope.Joinerインタフェースに委譲するようになりました。この方が分かりやすいですし、今後条件の拡張しやすくていいですね。

 

JEP 507:Primitive Types in Patterns, instanceof, and switch (Third Preview)

パターンマッチングにプリミティブ型を使用できるようにするのがJEP 507です。

JEP 507は変更なしだったのですが、まだドラフトなのですが次のJEPで変更が入ったので、Standardになるのは当分先になってしまいました。残念。

 

Experimental

Experimental JEPは1つだけで、JFRの機能追加に関してです。

 

JEP 509: JFR CPU-Time Profiling (Experimental)

CPUの計測を行うJFRのイベントが追加されました。ただし、Linuxだけです。

イベントはjdk.CPUTimeSampleです。

今のところ、新しいJEPは提案されていないようなので、Java 26でStandard JEPになるかもしれません。

 

Incubator

最後のIncubator JEPは、Value Classが導入するまでずっとIncubatorのままのあれです。

 

JEP 508: Vector API (Tenth Incubator)

ベクトル計算を行うためのVector APIですが、JEP 508ではいくつかの変更がありました。ただ、Value Classが導入されるまでIncubatorのままなので、今それを調べてもなぁ... と調べるモチベーションが上がりません😰

 

まとめ

Java 25はLTSということもあり、Standard JEPが多いリリースでした。

特にJEP 512のメインクラスの省略は意外に便利です。また、JEP 506のScoped Valueも正式化したので、今後ThreadLocalの置き換えが進むかもしれません。

 

ところで、Java 25とは関係ないのですが、ずっとドラフトのままだったValue Classがサブミットされて、通常のJEPにやっとなりました。JEP 401: Value Classes and Objectsです。

まだ、ターゲットバージョンが決まっていないのですが、早ければJava 26からPreviewが始まるかもしれません。

いつ導入されるのか全然わからなかったValue Classですが、やっと一歩踏み出した感じですね。

2025/10/04

JEPで語るJava 25 その1

このエントリーをはてなブックマークに追加

さて、Java 25のJEP (JDK Enhancement Proposal)です。

Java 25のJEPは18個。そのうち、Standard JEPが12で、Preview JEPが4、Experimental JEPとIncubator JEPがそれぞれ1つという内訳です。

ちなみに、gihyo.jpでJEPの概要について記事を書きましたので、ざっと分かればよいという方はこちらをご参照ください。

 

Java 25リリース 初心者向け機能や起動時間短縮など

https://gihyo.jp/article/2025/09/java25

 

Java 25のJEPの一覧を次に示します。

  • 470: PEM Encodings of Cryptographic Objects (Preview)
  • 502: Stable Values (Preview)
  • 503: Remove the 32-bit x86 Port
  • 505: Structured Concurrency (Fifth Preview)
  • 506: Scoped Values
  • 507: Primitive Types in Patterns, instanceof, and switch (Third Preview)
  • 508: Vector API (Tenth Incubator)
  • 509: JFR CPU-Time Profiling (Experimental)
  • 510: Key Derivation Function API
  • 511: Module Import Declarations
  • 512: Compact Source Files and Instance Main Methods
  • 513: Flexible Constructor Bodies
  • 514: Ahead-of-Time Command-Line Ergonomics
  • 515: Ahead-of-Time Method Profiling
  • 518: JFR Cooperative Sampling
  • 519: Compact Object Headers
  • 520: JFR Method Timing & Tracing
  • 521: Generational Shenandoah

gihyo.jpの記事でも、関連したJEPをまとめて扱いましたが、ここでもそうしていきます。

  • 初心者向け: 511, 512
  • 小さいオブジェクト: 513, 519
  • Project Leyden: 514, 515
  • 安全性、セキュリティ: 503, 510
  • モニタリング: 518, 520
  • その他: 506, 521
  • Preview: 470, 502, 505, 507
  • Experimental: 509
  • Incubator: 508

なお、セキュリティに関するJEP 470とJEP 510は、いつものごとく説明を省略します。

 

初心者向け

今までの初心者向け機能というと、Javaのソースファイルをコンパイルしなくても実行することができるJEP 330とJEP 458がありました。

これらは、コンパイルして実行しなくてはいけないという手間を減らすための機能です。

しかし、Javaが初心者向けではないといわれるもう1つの要因が残っていました。たとえば、"Hello, World!"を出力するだけのプログラムでもメインクラスを書かなくてはいけないなど、どうしても記述量が多くなってしまうという点です。

それに対し、Java 25では2つのJEPでこれに対応しています。

 

JEP 511: Module Import Declarations

IDEを使ってプログラムを書いていると、import文はIDEが勝手に整理してくれるのでほとんど気にしないですけど、初心者にとってはハードルが高いのも確か。

あっという間にimport文が何十行にもなっていたりするんですよね。しかも、なぜかimport文でアスタリスクを使うのは嫌われているし。あれは何で嫌われているんでしょうね?

それに対し、JEP 511はimport文をモジュール単位で指定できるようにしましょうというJEPです。

書き方は簡単です。Mモジュールで定義されたクラスやインタフェースなどを使用する場合、次のように記述します。

import module M;

 

moduleキーワードが入るだけですね。

たとえば、GUIでAWTやSwingを使いたいのであれば、次のようになります。

import module java.desktop;

 

しかし、AWTを使うと、Listが問題になります。ようするに、java.awt.Listなのか、java.util.Listなのかという問題です。

import module java.desktop;
import module java.base;

  ...

  List list = ... // これは java.awt.List か java.util.List か?

 

こういう時は、モジュールインポートより通常のインポート文の方が優先されるので、次のように書きます。

import module java.desktop;
import java.util.List;

  ...

  List list = ... // これは java.util.List

 

あくまでも説明のためなので、RAW型を使うなとか言わないでくださいね。

また、アスタリスクで指定でも大丈夫です。

 

import module java.desktop;
import java.util.*;

  ...

  List list = ... // これは java.util.List

 

単一のインポート > アスタリスクでインポート > モジュールインポート の順に優先度が高くなります。

JEP 511ではあいまいなインポートを解決するためにインポート文を記述するのをシャドーイングという言葉で説明しているます。そういわれれば分かるものの、さくらばには、そのニュアンスはなかなか理解できないのでした。

 

JEP 512 Compact Source Files and Instance Main Methods

もう1つの初心者向け機能がJEP 512です。

JEP 512ではプログラム実行の起点であるmainメソッドを簡略化するための仕様を取り決めています。

簡略化できるのは次の4項目です。

  • メインクラスの省略
  • mainメソッドの簡略化
  • java.baseモジュールのインポートの省略
  • 標準入出力を行うクラスの導入

 

では、おなじみのHello, World!で考えてみましょう。ここではHello.javaファイルに記述しているものとします。

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

 

メインクラスの省略

プログラムの実行という意味では起点となるmainメソッドだけあればよいのですが、Javaでメソッドだけ単体で定義するわけにはいかなかったので、メインクラスが必要でした。

これに対して、Java 25ではメインクラスが省略可能になりました。

public static void main(String[] args) {
    System.out.println("Hello World");
}

 

これだけでかなりスッキリしますね。

 

mainメソッドの簡略化

メインクラスが省略できるようになったのに、わざわざアクセス修飾子のpublicやstaticなどは書く意味がありません。しかも、初心者にpublicやstaticを教えるのもなかなか大変なので、できれば触れたくない部分でもあります。

ということで、Java 25ではmainメソッドも大幅に簡略して書くことができるようになりました。

メインクラスも省略すると、"Hello, World!"を出力するには以下のようになりました!

void main() {
    System.out.println("Hello World");
}

 

アクセス修飾子もstaticも引数も省略可能です。もちろん、書いてもだいじょうぶです。

 

さくらばの予想では、省略してもコンパイル時に補完されてpublic static void main(String... args)としてクラスファイルに記述されると思っていました。

ところが、クラスファイルをjavapで見てみるとvoid main()のままでした。

ということは、javaの起動時にmainという名前のメソッドを検索して実行するように動作が変わったようです。

 

では、mainメソッドがオーバーロードされていたらどうなるでしょう。

void main(String arg) {
    System.out.println("Hello World 3");
}

void main(String... args) {
    System.out.println("Hello World 2");
}

void main() {
    System.out.println("Hello World 1");
}

 

さて、実行すると1、2、3のどちらが出力されるでしょう。

> java Hello.java
Hello World 2
    
>

 

やはり、元々のpublic static void main(String... args)に近いシグネチャーのオーバーロードが選ばれるようです。

 

ところで、void main()はstaticがついていないので、クラスファイルにはインスタンスメソッドとして定義されています。

この場合、他のメソッドはコールできるのでしょうか?

void hello() {
    System.out.println("Hello, World!");
}

static void staticHello() {
    System.out.println("Hello, Static World!");
}

void main() {
    hello();
    staticHello();
}

 

インスタンスメソッドとstaticメソッドを定義してコールしてみました。実行してみると...

> java Hello.java
Hello, World!
Hello, Static World!
    
>

 

インスタンスメソッドもstaticメソッドもコールすることができました。

ただし、mainメソッドをstaticで定義してしまうと、インスタンスメソッドをコールする部分でコンパイルエラーになります。

void hello() {
    System.out.println("Hello, World!");
}

static void staticHello() {
    System.out.println("Hello, Static World!");
}

static void main() {
    hello();
    staticHello();
}

 

> javac Hello.java
Hello.java:10: エラー: staticでないメソッド hello()をstaticコンテキストから参照することはできません
    hello();
    ^
エラー1個
    
>

 

java.baseモジュールのインポートの省略

次はインポート文の簡略化です。

リストの要素の一覧を出力するプログラムを考えてみましょう。

Listインタフェースを使うのですから、import java.util.List;が必要になりますが、JEP 511のおかげでimport module java.base;で済みます。

ところが、このインポート文は省略することができます。java.langパッケージのインポートは省略できるのと同じように、java.baseモジュールのインポートを省略できるのです。

したがって、次のようにListインタフェースを使用していても、インポート文は書かずに済みます。

void main() {
    List.of("Alpha", "Bravo", "Charlie")
        .forEach(System.out::println);
}

 

ただし、これはメインクラスを省略した場合だけです。

class Hello {
    void main() {
        List.of("Alpha", "Bravo", "Charlie")
            .forEach(System.out::println);
    }
}

 

> javac Hello.java
Hello.java:3: エラー: シンボルを見つけられません
        List.of("Alpha", "Bravo", "Charlie")
        ^
  シンボル:   変数 List
  場所: クラス Hello
エラー1個
    
>

 

メインクラスを書くのであれば、インポート文も記述しましょう。

import module java.base;

class Hello {
    void main() {
        List.of("Alpha", "Bravo", "Charlie")
            .forEach(System.out::println);
    }
}

 

> java Hello.java
Alpha
Bravo
Charlie
    
>

 

標準入出力を行うクラスの導入

最後が標準入出力を行うクラスです。

意外にSystem.outとSystem.inは説明するのが難しいんですよね。そこで、標準出力と入力の両方を扱うためのjava.lang.IOクラスが導入されました。

IOクラスでは、標準入出力のための最低限のメソッドだけが定義されています。もちろん標準エラー出力も扱いません。

定義してあるメソッドは以下の5種類です。

  • static void print(Object obj)
  • static void println()
  • static void println(Object obj)
  • static String readln()
  • static String readln(String prompt)

 

一番使うのはprintln(Object obj)でしょうね。標準入力もあるので、次のようなプログラムも簡単に記述できます。

void main() {
    for(;;) {
        var to = IO.readln("To: ");
        IO.println("Hello, " + to + "!");
    }
}

 

> java Hello.java
To: Wolrd
Hello, Wolrd!
To: Java
Hello, Java!
To:

 

ここまで省略できたり簡略化されたりすると、かなり書くのも楽になりますね。

 

小さいオブジェクト

 

Javaは歴史的経緯もあって少数のミュータブルで巨大なオブジェクトを扱うことが多かったのですが、Java SE 8から関数型の機能が導入されてから多数のイミュータブルな小さいオブジェクトを扱うように変わってきています。

イミュータブルな小さいオブジェクトを効率的に扱うようにするのが、Project ValhallaのValue Classです。

しかし、Value Classの導入には言語仕様やVM仕様の大幅な変更が伴うため、おいそれと導入するわけにはいきません。そこで、Value Classがまとまる前に、できるものから導入していこうというのが現在のステータスです。Java 25ではJEP 513: Flexible Constructor Bodiesが、それに相当します。

もう一方のJEP 519: Compact Object HeadersはValue Classとは直接関連はないのですが、多量の小さいオブジェクトを扱うためにオブジェクトヘッダーをコンパクト化しましょうというJEPになります。

 

JEP 513: Flexible Constructor Bodies

Java 24の時にも書きましたが、コンストラクター内でスーパークラスや自分自身のコンストラクターをコールするのは、コンストラクターの先頭と決まっていました。

これに対し、自分自身のフィールドの初期化を行ってから、他のコンストラクターをコールできるようになりました。

Value Classはイミュータブルなクラスですが、今までのオブジェクトの初期化順だと、初期化前のフィールドにスーパークラスからアクセスできてしまうという問題があります。

問題になるようなことはほとんどないはずですが、イミュータブルなオブジェクトにとっては値が不定な時にアクセスできてしまうというのは困ってしまいます。

そこれで、JEP 513でValue Classが導入される前に、この問題ををつぶしておこうというわけです。

 

JEP 519: Compact Object Headers

オブジェクトヘッダーをコンパクト化するJEP 519は、Java 24のJEP 450と特に変更はないようです。なので、Java 24のJEPで語るをご覧いただければと思います。

結局、Value Classのビットはそのままになったようです。

また、JEP 519にはJava 24での実行時オプションがのったままになっているのですが、これはJava 25でもそのままなのかどうかイマイチ分かりません。

デフォルトでコンパクトヘッダーになっているかどうかは、小さいオブジェクトをいっぱい使って、JFRとかでヒープの使用量を見れば分かるかな? 時間があれば、やってみます。

 

長くなってしまったので、残りは次回!

2025/09/16

JEPでは語れないJava 25

このエントリーをはてなブックマークに追加

毎度おなじみ半年ぶりのJavaのアップデートです。

OpenJDK的にはLTSかどうかは関係なく、LTSかどうかはOracleなどのJDKディストリビューターによります。OracleがJava 25をLTSとするので、右にならえで他のディストリビューターもLTSにしてますね。

OpenJDKはLTSとは関係ないとはいうものの、実際にはLTSに間に合わせて機能を入れ込むということはありそうです。実際、Java 25のStandard JEPは12と結構な数になっています。

Java 25のJEPの一覧はこちら。

  • 470: PEM Encodings of Cryptographic Objects (Preview)
  • 502: Stable Values (Preview)
  • 503: Remove the 32-bit x86 Port
  • 505: Structured Concurrency (Fifth Preview)
  • 506: Scoped Values
  • 507: Primitive Types in Patterns, instanceof, and switch (Third Preview)
  • 508: Vector API (Tenth Incubator)
  • 509: JFR CPU-Time Profiling (Experimental)
  • 510: Key Derivation Function API
  • 511: Module Import Declarations
  • 512: Compact Source Files and Instance Main Methods
  • 513: Flexible Constructor Bodies
  • 514: Ahead-of-Time Command-Line Ergonomics
  • 515: Ahead-of-Time Method Profiling
  • 518: JFR Cooperative Sampling
  • 519: Compact Object Headers
  • 520: JFR Method Timing & Tracing
  • 521: Generational Shenandoah

 

目だったところでいうと、Scoped Valueがやっと正式になりましたね。でも、もう1つのStructured ConcurrencyがまだPreview...

ニーズ的にはThreadLocalを置き換えるScoped Valueの方が高いから、こちらが優先されたのかもしれません。

初学者向け機能であるJEP 511とJEP 512が正式導入されたのもうれしいところです。

Project LeydenのJEP 514とJEP 515も正式になって、GraalVMのネイティブイメージではない選択肢が増えてきたのはよいことですね。

JEPについての詳しい解説は「JEPで語る」に書く予定です。

 

さて、JEPで語れない方です。Java 25はそこそこ多いです。

Java 25はjava.baseモジュール以外にも変更があるので、そちらも紹介します。しかし、セキュリティ関連はいつものごとく省略させてください。

 

廃止になったAPI

Java 24で削除されたAPIはメソッドとコンストラクタが1つずつ。いずれもSwing関連のAPIです。

メソッド

SwingのLoo&FeelのSynthに関するメソッドが削除されました。

  • javax.swing.plaf.synth.SynthLookAndFeel.load(URL url)

このメソッドはLook&Feelの設定を書いたファイルを読み込むためのものなのですが、安全性に問題があるということでJava 21でforRemoval=trueになっていました。

まぁ、Look&FeelのSynthを使ったアプリケーションはほぼないと思いますし、他の手段もあるので、困る人も少ないでしょう。

 

コンストラクタ

SwingのスライダーのUIクラスのコンストラクターが削除されました。

  • javax.swing.plaf.basic.BasicSliderUI.BasicSliderUI()

このデフォルトコンストラクターは間違って公開されてしまったらしいですよw

本来は引数にJSliderクラスをとるコンストラクターを使用するので、まぁ使っている人はいないとは思いますが、使っていた場合は引数をとる方のコンストラクターに書き換えてください。

 

廃止予定に追加されたAPI

Java 25では、Java 24でのセキュリティマネージャーの削除に伴って、パーミッション系のクラスがforRemoval=trueになっています。

クラス

  • java.io.FilePermission
  • java.io.SerializablePermission
  • java.lang.RuntimePermission
  • java.lang.management.ManagementPermission
  • java.lang.reflect.ReflectPermission
  • java.net.NetPermission
  • java.net.URLPermission
  • java.nio.file.LinkPermission
  • java.security.SecurityPermission
  • java.security.UnresolvedPermission
  • java.util.PropertyPermission
  • java.util.logging.LoggingPermission
  • javax.management.MBeanPermission
  • javax.management.MBeanServerPermission
  • javax.management.MBeanTrustPermission
  • javax.management.remote.SubjectDelegationPermission
  • javax.net.ssl.SSLPermission
  • javax.security.auth.AuthPermission
  • javax.security.auth.PrivateCredentialPermission
  • javax.security.auth.kerberos.DelegationPermission
  • javax.security.auth.kerberos.ServicePermission

 

例外

  • javax.management.modelmbean.XMLParseException

 

メソッド

  • java.net.HttpURLConnection.getPermission
  • java.net.URLConnection.getPermission
  • javax.management.modelmbean.DescriptorSupport.toXMLString

もし、パーミッション系のAPIを使っているのであれば、早めに他の手段に置き換えた方がいいですね。

 

追加/変更されたAPI

今回のJava 25は、Java 24とは異なりいろいろ追加されています。しかも、JEPに関連のないAPIの変更がいろいろあります。

とはいうものの、使うかと言われたら微妙なものが多いですね。

今回もjava.baseモジュール以外の変更は少ないので、java.baseモジュールだけです。また、セキュリティ系のAPIは省略させてください。

 

java.base/java.ioパッケージ

JEP 512で正式に導入されたIOクラスは、Java 24まではjava.ioパッケージだったのですが、Java 25からjava.langパッケージに変更されました。また、同様にConsoleクラスにPreviewでprint(Object obj)メソッドなどが追加されていましたが、結局キャンセルになったようです。

Readerクラス

なぜかJava 25にもなって、Readerクラスに一括読み込みのメソッドが追加されました。

  • String readAllAsString()
  • Line<String> readAllLines()

今までReader系のクラスでは逐次読み込みばかりだったので、便利なのは確かなのですが、なぜ今??

やっぱり、Files.readAllLinesメソッドを使うようになってから、逐次読み込みなんか書いてられないということなのでしょうか?

おもしろいのは、JavadocのThrowsにOutOfMemoryError例外が記載されていること。Stringクラスがあふれちゃうことがあるかららしいです。

 

java.base/java.langパッケージ

Java 25では、Unicodeのバージョンアップはないので、Unicodeに関するクラスや列挙型には変更ありません。

その代わりといってはなんですが、Math/StrictMathクラスにメソッドがいろいろ追加されています。

また、java.ioパッケージのところでも言及しましたが、IOクラスがjava.langパッケージに変更されてきています。

 

CharSequenceインタフェース

文字列の部分配列を取得するためのメソッドが追加されました。インタフェースなので、もちろんdefaultメソッドです。

  • default void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)

getCharsメソッドはStringクラスにはあったので、それをCharSequenceでも使えるようにした感じですね。

引数のdstに結果が返るというのは今時ではないですけど、StringクラスのgetCharsメソッドははじめからあるメソッドなのでしかたないです。

 

IOクラス

JEP 512で、標準入出力からの入出力を行うために導入されたのがIOクラスです。Java 24の時とパッケージが変わっただけで、メソッドは変更なしです。

  • static void print(Object obj)
  • static void println()
  • static void println(Object obj)
  • static String readln()
  • static String readln(String prompt)

今までSystem.out.println("Hello, World");と書いていたのが、IO.println("Hello, World!");になります。

初心者にとって学ぶことが少しでも減れば、学ぶときのハードルが下がりますね。

 

Mathクラス/StrictMathクラス

Math/StrictMathクラスにメソッドが7つ追加されました。

歴史的な経緯でMathクラスとStrictMathクラスは別々のクラスになっていますが、実際にはStrictMathクラスは内部でMathクラスを呼び出しているだけなので、今となっては同じクラスとして扱っても構わないです。

  • static int powExact(int x, int n)
  • static long powExact(long x, int n)
  • static int unsignedMultiplyExact(int x, int y)
  • static long unsignedMultiplyExact(long x, int y)
  • static long unsignedMultiplyExact(long x, long y)
  • static int unsignedPowExact(int x, int n)
  • static long unsignedPowExact(long x, int n)

今まで、累乗演算は引数がdoubleのものだけでしたが、整数型が加わりました。また、累乗計算も含めて、符号なし計算も追加されています。

これまで、Mathクラスでは符号なし計算はunsignedMultiplyHighメソッドだけだったので、今後も増えるのかもしれません。

 

ScopedValueクラス

JEP 506でPreviewがはずれて、正式に導入されたScopedValueクラスです。

Java 24との違いはorElseメソッドの引数にnullが許されなくなったことぐらいですね。

ScopedValueクラスについてはここではなく、JEPで語る方で紹介します。

 

StableValueインタフェース

JEP 502でPreviewとして追加されたのが、StableValueインタフェースです。

同じパッケージにScopedValueとStableValueという同じような名前のクラス/インタフェースができたので、ごっちゃになりやすくて困ってますw

StableValueインタフェースもJEPで語る方で紹介します。

 

java.base/java.lang.classfileパッケージ

ClassFileクラスにバージョンを表す定数が追加されています。

 

ClassFileクラス

ClassFileクラスでは、クラスファイルのバージョンを表す定数が定義されていますが、それが追加されました。

  • static final int JAVA_25_VERSION

この値はJava 25だから25というわけではなく、クラスファイルのバージョンの69になっています。

 

java.base/java.lang.classfile.constantpoolパッケージ

java.lang.classfile.constantpoolパッケージではクラスファイルのコンスタントプールに関する構造体を表すインタフェースが定義されています。

このコンスタントプールの構造体を表すインタフェースは、java.lang.constantパッケージで定義されているインタフェースをモデル化したものです。そこで、この対応関係を調べるためのメソッドが追加されました。

Class-File APIは新しいAPIですが、新しいからこそ、まだいろいろ追加されるのかもしれません。

 

ClassEntryインタフェース

コンスタントプールで参照型を表すCONSTANT_Class_info構造体をモデル化したのがClassEntryインタフェースです。ClassEntryインタフェースはjava.lang.constant.ClassDescインタフェースによってモデル化されたインタフェースになります。

このClassDescインタフェースとマッチするかどうかを調べるためのメソッドが追加されています。

  • boolean matches(ClassDesc desc)

ClassEntryオブジェクトとClassDescオブジェクトが表している参照型クラスが同じであればtrueが返ります。また、引数のdescがプリミティブ型の場合はfalseになります。

 

MethodTypeEntryインタフェース

コンスタントプールでCONSTANT_MethodType_info構造体をモデル化したのがMethodTypeEntryインタフェースです。MethodTypeEntryインタフェースはjava.lang.constant.MethodTypeDescインタフェースによってモデル化されたインタフェースです。

  • boolean matches(MethodTypeDesc desc)

同じメソッドを表している場合、trueが返ります。

 

ModuleEntryインタフェース

コンスタントプールでCONSTANT_Module_info構造体をモデル化したのがModuleEntryインタフェースです。対応するのが、java.lang.constant.ModuleDescインタフェースです。

  • boolean matches(ModuleDesc desc)

同じモジュールを表している場合、trueが返ります。

 

PackageEntryインタフェース

コンスタントプールでCONSTANT_Package_info構造体をモデル化したのがPackageEntryインタフェースです。対応するのが、java.lang.constant.PackageDescインタフェースです。

  • boolean matches(PackageDesc desc)

同じパッケージを表している場合、trueが返ります。

 

StringEntryインタフェース

コンスタントプールでCONSTANT_String_info構造体をモデル化したのがStringEntryインタフェースです。Stringクラスは参照型なので、本来であれば対応するのはClassDescインタフェースですが、文字列リテラルは特別扱いです。

  • boolean equalsString(String value)

StringEntryインタフェースはmatchesメソッドではなく、文字列と直接比較するequalsStringメソッドが追加されています。

 

Utf8Entryインタフェース

コンスタントプールでは、文字列リテラルだけでなくクラス名やメソッド名はUTF-8で保持されています。それを表すCONSTANT_UTF8_info構造体をモデル化したのがUtf8Entryインタフェースです。

Utf8EntryインタフェースはすでにequalsStringメソッドは定義してあるのですが、クラス名やメソッド名とマッチさせるメソッドが追加されました。

  • boolean isFieldType(ClassDesc desc)
  • boolean isMethodType(MethodTypeDesc desc)

 

java.base/java.lang.reflectパッケージ

java.lang.reflectパッケージはいつも通り新しいバージョンに合わせた定数が追加されているのですが、Java 25ではアクセスフラグにも追加があります。

AccessFlag列挙型

アクセスフラグのセットを作成するmaskToAccessFlagsメソッドに、クラスファイルのバージョンを指定できるオーバーロードが追加されました。

  • static Set<AccessFlag> maskToAccessFlags(int mask, AccessFlag.Location location, ClassFileFormatVersion cffv)

 

AccessFlag.Location列挙型

アクセスフラグを適用できる場所を示しているのがAccessFlag.Location列挙型です。

列挙型ですが、メソッドが追加されました。

  • int flagsMask()
  • int flagsMask(ClassFileFormatVersion cffv)
  • Set<AccessFlag> flags()
  • Set<AccessFlag> flags(ClassFileFormatVersion cffv)

たとえば、MODULEであればフラグマスクはACC_OPEN | ACC_SYNTHETIC | ACC_MANDATEDになります。

クラスファイルのバージョンによって使用できるフラグマスクが異なるので、バージョンを指定するオーバーロードがあるわけです。

 

ClassFileFormatVersion列挙型

いつものように、Java 25に対応する定数が追加されました。

  • RELEASE_25

 

java.base/java.nioパッケージ

なぜかCharBufferクラスにメソッドが追加されました。

CharBufferクラス

一括取得のメソッドが追加されたのですが、今まで使用していたgetメソッドと何が違うの?という感じです。

  • public void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)

Javadocにも書かれているのですが、このメソッドはgetメソッドを使った次の式と同じ動作になります。

buffer.get(position() + srcBegin, dst, dstBegin, srcEnd - srcBegin);

記述が簡単になったというわけでもないですし、何のために追加されたんでしょうね。

 

java.base/java.utilパッケージ

CurrencyクラスとTimeZoneクラスにStreamインタフェースを返すユーティリティメソッドが追加されました。

Currencyクラス

使用可能な通貨を返すメソッドが追加されています。

  • static Stream<Currency> availableCurrencies()

これまではgetAvailableCurrenciesメソッドが使えましたが、このメソッドは返り値がSetインタフェースでした。availableCurrenciesメソッドは返り値の型がStreamインタフェースなので、Stream APIで扱いやすくなっています。

 

TimeZoneクラス

TimeZoneクラスでも使用可能なIDを返すメソッドが追加されています。

  • static Stream<String> availableIDs()
  • static Stream<String> availableIDs(int rawOffset)

TimeZoneもgetAvailableIDsメソッドがありましたが、戻り値の型がStringクラスの配列でした。追加された2メソッドはStreamインタフェースになっています。

 

java.base/java.util.concurrentパッケージ

ForkJoinPoolクラスに大きな変更がありました。

ForkJoinPoolクラス

ForkJoinPoolクラスはこれまでExecutorインタフェース、ExecutorServiceインタフェース、AutoClosableインタフェースを実装していましたが、新たにScheduledExecutorServiceインタフェースを実装するようになりました。

このため、ScheduledExecutorServiceインタフェースで定義されているメソッドが追加されています。

  • void cancelDelayedTasksOnShutdown()
  • long getDelayedTaskCount()
  • ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
  • <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit)
  • ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
  • ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)
  • <V> ForkJoinTask<V> submitWithTimeout(Callable<V> callable, long timeout, TimeUnit unit, Consumer<? super ForkJoinTask<V>> timeoutAction)

ScheduledExecutorServiceインタフェース由来のメソッドはscheduleで始まる4つのメソッドです。

scheduleメソッドはタスク処理の開始を指定された時間だけ遅延させるメソッドです。オーバーロードは引数がRunnableインタフェースか、Callableインタフェースの違いです。

たとえば、10秒後に現在時刻を出力するのであれば、次のようになります。

jshell> var pool = new ForkJoinPool(2)
jshell> pool.schedule(() -> IO.println(LocalDateTime.now()), 10, TimeUnit.SECONDS)
$2 ==> java.util.concurrent.DelayScheduler$ScheduledForkJoinTask@7a07c5b4[Wrapped task = $Lambda/0x0000000088048858@26a1ab54]

jshell> IO.println(LocalDateTime.now())
2025-09-12T18:42:45.527033900

jshell> 2025-09-12T18:42:53.898474800

比較のために、IO.printlnをもう1度実行していますが、手で打ち込んでいるので遅いですねw

scheduleAtFixedRateメソッドとscheudleWithFixedDelayの違いは、前者が同じ周期でタスクを実行するのに対し、後者はタスクが終わってから次のタスクまでの遅延が常に同じということです。

たとえば10秒ごとで処理に1秒かかる場合、scheduleAtFixedRateメソッドであれば10秒ごとにタスクが処理されるのに対し、scheduleWithFixedDelayメソッドだと11秒ごとに処理が行われます。

 

jshell> var pool = new ForkJoinPool(2)
jshell> pool.scheduleAtFixedRate(() -> IO.println(LocalDateTime.now()), 0, 10, TimeUnit.SECONDS)
$4 ==> java.util.concurrent.DelayScheduler$ScheduledForkJoinTask@6433a2[Wrapped task = $Lambda/0x0000000082048210@5910e440]

jshell> 2025-09-12T19:32:30.296824400
2025-09-12T19:32:40.258892800
2025-09-12T19:32:50.264887400
2025-09-12T19:33:00.258888800
2025-09-12T19:33:10.257071800

 

jshell> var pool = new ForkJoinPool(2)
jshell> pool.scheduleWithFixedDelay(() -> { IO.println(LocalDateTime.now()); try { Thread.sleep(1_000); } catch (Exception e){}}, 0, 10, TimeUnit.SECONDS)
$4 ==> java.util.concurrent.DelayScheduler$ScheduledForkJoinTask@7a07c5b4[Wrapped task = $Lambda/0x0000000033048858@26a1ab54]

jshell> 2025-09-12T19:48:54.864989800
2025-09-12T19:49:05.866353500
2025-09-12T19:49:16.869035100
2025-09-12T19:49:37.873515600

ちょっと分かりにくいかもしれないですけど、後者は11秒周期になっています。

周期的なタスクを中止するには、返り値のScheduledFutureオブジェクトに対し、cancelメソッドをコールします。

cancelDelayedTasksOnShutdownメソッドは、ForkJoinPoolオブジェクトをシャットダウンする時に遅延させた未実行のタスクをキャンセルする場合に使用します。

getDelayedTaskCountメソッドは、遅延させた未実行のタスクの数を返すメソッドです。

そして、最後のsubmitWithTimeoutメソッドだけがScheduledExecutorServiceインタフェースに関連のないメソッドで、タスク処理にタイムアウトを設定できるメソッドです。

 

ForkJoinPoolは、ForkJoinTaskと合わせることで分割統治とWork-Stealingでタスクを並行処理するために導入されたのですが、Work-Stealingだけ使いたいということが増えてきているんでしょうね。

 

java.base/java.util.zipパッケージ

DeflaterクラスとInflaterクラスがAutoClosableインタフェースを実装するようになりました。これによりcloseメソッドが追加されています。

Deflaterクラス/Inflaterクラス

AutoClosableインタフェースの実装に伴い、closeメソッドが追加されました。

  • void close()

これまでは、処理の終了時にはendメソッドを使用していましたが、AutoClosableインタフェースを実装したことによりtry-with-resources構文を使用できるようになりました。

 

その他

Javadoc

Javadocの検索候補の表示が変更されています。

たとえば、検索窓にStrまで入力した場合、Java 24だと以下のようになります。

これに対して、Java 25だと次のようになります。

Java 24では検索候補のパッケージやクラスの前にモジュールやパッケージが表示されていますが、Java 25では検索候補がまず表示されます。そのモジュールやパッケージはウィンドウの幅が十分にある場合は右側、なければ検索候補の下に表示されるようになりました。

ちなみに、Java 26でもJavadocが変更されていて、検索候補を右クリックで別タブでの表示などを選択できるメニューが表示されます。他にも、細かな変更がされています。

すでに、実装されているので、Java 26のアーリーアクセスビルドで試すことができます。

 

まとめ

APIの変更は多いものの、普通のアプリケーションでは使わなそうなものが多い感じです。

IOクラスが一番使うかもしれませんが、ほとんどの人はSystem.outを使い続けるような気が...

並行処理を書くことがあるのであれば、ScopedValueクラスは使うかもしれません。しかし、そもそもThreadLocalクラスでさえ通常は使わないので、ScopedValueクラスを使うとしても限定的でしょうね。

というか、こういう並行処理でこういう複数のオブジェクトで状態を共有というのは、スケールしなくなるし、いいことはないです。ThreadLocalクラスが使われているところをScopedValueクラスに置き換えるということはあるかもしれませんが、まずはその設計を見直した方がいいような気が...

 

さて、次のエントリーではJEPに関して簡単な説明を加えていく予定です。