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とかでヒープの使用量を見れば分かるかな? 時間があれば、やってみます。

 

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

0 件のコメント: