フューチャー技術ブログ

Java 23 リリース記念連載 | 第3回 JDK 23 新機能紹介

はじめに

JDK 23のリリースを受けてバージョン21~23にかけての変更点を紹介する連載企画の3本目の記事です。

2024 年 9 月 17 日(米国時間 ⁠)に「JDK 23」が リリース されました。

本記事では JDK 23 でのアップデート内容から(Preview や Incubator の変更点を除き)正式に採用となったアップデートについて紹介します。

JEPs

JDK 23 で対応された主要な 12 の JEP(Java Enhancement Proposals)は次の通りです。

Oracle の プレスリリース をベースにカテゴライズしています。

カテゴリ JEP タイトル
言語機能 JEP 455 Primitive Types in Patterns, instanceof, and switch (Preview)
言語機能 JEP 476 Module Import Declarations (Preview)
言語機能 JEP 477 Implicitly Declared Classes and Instance Main Methods (Third Preview)
言語機能 JEP 482 Flexible Constructor Bodies (Second Preview)
ライブラリ JEP 466 Class-File API (Second Preview)
ライブラリ JEP 469 Vector API (Eighth Incubator)
ライブラリ JEP 472 Stream Gatherers (Second Preview)
ライブラリ JEP 480 Structured Concurrency (Third Preview)
ライブラリ JEP 481 Scoped Values (Third Preview)
パフォーマンス/ランタイム JEP 474 ZGC: Generational Mode by Default
ツール JEP 467 Markdown Documentation Comments
スチュワードシップ JEP 471 Deprecate the Memory-Access Methods in sun.misc.unsafe for Removal:

本記事ではこの中から、JEP 467, JEP 474, JEP 471 について深掘りしていきます。

JEP 467: Markdown Documentation Comments

https://openjdk.org/jeps/467

これまで HTML と Javadoc タグ(例. @param, @return)で記述していた Javadoc を Markdown 形式で記述できるようになりました。
これは、API のドキュメントを開発者にとって、書きやすく、そして読みやすくすることを目的としています。

従来の記法との比較

Markdown の使用例について、 java.lang.Object.hashCode のドキュメントを題材として、従来の記法と比較して見てみましょう。これは JEP の中でも紹介されている例です。

従来の記法

/**
* Returns a hash code value for the object. This method is
* supported for the benefit of hash tables such as those provided by
* {@link java.util.HashMap}.
* <p>
* The general contract of {@code hashCode} is:
* <ul>
* <li>Whenever it is invoked on the same object more than once during
* an execution of a Java application, the {@code hashCode} method
* must consistently return the same integer, provided no information
* used in {@code equals} comparisons on the object is modified.
* This integer need not remain consistent from one execution of an
* application to another execution of the same application.
* <li>If two objects are equal according to the {@link
* #equals(Object) equals} method, then calling the {@code
* hashCode} method on each of the two objects must produce the
* same integer result.
* <li>It is <em>not</em> required that if two objects are unequal
* according to the {@link #equals(Object) equals} method, then
* calling the {@code hashCode} method on each of the two objects
* must produce distinct integer results. However, the programmer
* should be aware that producing distinct integer results for
* unequal objects may improve the performance of hash tables.
* </ul>
*
* @implSpec
* As far as is reasonably practical, the {@code hashCode} method defined
* by class {@code Object} returns distinct integers for distinct objects.
*
* @return a hash code value for this object.
* @see java.lang.Object#equals(java.lang.Object)
* @see java.lang.System#identityHashCode
*/

Markdown 記法

/// Returns a hash code value for the object. This method is
/// supported for the benefit of hash tables such as those provided by
/// [java.util.HashMap].
///
/// The general contract of `hashCode` is:
///
/// - Whenever it is invoked on the same object more than once during
/// an execution of a Java application, the `hashCode` method
/// must consistently return the same integer, provided no information
/// used in `equals` comparisons on the object is modified.
/// This integer need not remain consistent from one execution of an
/// application to another execution of the same application.
/// - If two objects are equal according to the
/// [equals][#equals(Object)] method, then calling the
/// `hashCode` method on each of the two objects must produce the
/// same integer result.
/// - It is _not_ required that if two objects are unequal
/// according to the [equals][#equals(Object)] method, then
/// calling the `hashCode` method on each of the two objects
/// must produce distinct integer results. However, the programmer
/// should be aware that producing distinct integer results for
/// unequal objects may improve the performance of hash tables.
///
/// @implSpec
/// As far as is reasonably practical, the `hashCode` method defined
/// by class `Object` returns distinct integers for distinct objects.
///
/// @return a hash code value for this object.
/// @see java.lang.Object#equals(java.lang.Object)
/// @see java.lang.System#identityHashCode

比較してみて

HTML や Javadoc タグが Markdown に置き換わることで、ドキュメントコメントとして非常にすっきりとした印象を受けます。

  • HTML の <p> 要素が不要となり、空行が段落の区切りを示しています
  • HTML の <ul> および <li> 要素は、箇条書きマーカーに置き換えられています
  • HTML の <em> 要素は、アンダースコア(_)に置き換えられています
  • {@code ...} タグは、バックチック(`...`)で囲むことによって等幅フォントを示すように置き換えられています
  • 他のプログラム要素へのリンクを示す {@link ...} は、拡張された形式の参照リンクに置き換えられています

Markdown を用いた新しい記法について、詳しくみていきましょう。

新記法の仕様上のポイント

トリプルスラッシュの使用

コメントは従来の /**...*/ で囲む構文ではなく、 /// を各行の先頭に用いて記述します。これは実質、従来の // を用いた行末コメントに対して / を追加した形になります。
従来の /**...*/ を用いた複数行の形式を採用せずに、行末コメントの形式を採用した理由は、次のように説明されています。

  • 従来の /**...*/ コメントの形式では、コメント中に文字列として「*/」を含めることができません。次の例のようにドキュメントのコメントにコード例を書くことは一般的になっており、文字に制限のない行末コメントの形式が望ましいと考えられます。

    /// ```java
    /// int x = 0; /* 初期化 */
    /// ```
  • 従来の /**...*/ コメントは、各行の先頭にアスタリスクを付与することは任意のため、Markdown の構文(例. 強調表示やリスト項目)としてアスタリスクが付与されているのかどうかを厳密に判別できません。

    /**
    This is a comment.
    * List item
    More text.
    */

CommonMark の仕様を拡張

Markdown の記法としては標準的な仕様の 1 つである CommonMark の仕様に従います。
標準の CommonMark の仕様を拡張している点をいくつか紹介します。

参照リンク

参照リンクの拡張形式を使用することで、ほかの場所で宣言された要素へのリンクを作成できます。

ある要素への単純なリンクを作成するには、要素への参照を角括弧 [] で囲います。
たとえば java.util.List へリンクするためには [java.util.List] と書くか、コード中に java.util.Listimport 文があるのであれば [List] と書くだけです。

参照リンクはモジュール、パッケージ、クラス、フィールド、メソッドなどさまざまな要素に対して作成可能です。

/// - a module [java.base/]
/// - a package [java.util]
/// - a class [String]
/// - a field [String#CASE_INSENSITIVE_ORDER]
/// - a method [String#chars()]

任意のリンクテキスト含んだリンクを作成する場合は [text][element] という形式で記述します。
たとえば a list というテキストで java.util.List へのリンクを作成するには、[a list][List] と記述します。

/// - [the `java.base` module][java.base/]
/// - [the `java.util` package][java.util]
/// - [a class][String]
/// - [a field][String#CASE_INSENSITIVE_ORDER]
/// - [a method][String#chars()]
テーブル

テーブルは GitHub Flavored Markdown(GFM) のテーブル構文がサポートされています。
これは Markdown でテーブルを表現するデファクトスタンダードな記法であり、パイプを用いてシンプルなテーブルを表現します。

/// | Latin | Greek |
/// |-------|-------|
/// | a | alpha |
/// | b | beta |
/// | c | gamma |

アクセシビリティの観点からテーブルのキャプションや要約といった機能が必要になる場合もありますが、そのような機能はサポートされません。もしそのような機能が必要な場合は HTML 形式のテーブルの仕様が推奨されています。

シンタックスハイライト

こちらも Markdown を書く上ではお馴染みですが、コードブロックにシンタックスハイライト用の言語名を指定することがあります。Javadoc は標準でシンタックスハイライトをサポートしませんが、Javadoc 生成時に Prism などのライブラリを組み込むことで、シンタックスハイライトを有効にできます。

Javadoc タグのサポート

Javadoc タグはインラインタグとブロックタグの両方がマークダウン形式のコメントの中でサポートされます。

/// {@inheritDoc}
/// In addition, this methods calls [#wait()].
///
/// @param i the index
public void m(int i) ...

ちなみに {@inheritDoc} は実装しているインタフェースまたは継承したスーパークラスのドキュメントを参照するためのインラインタグになりますが、このとき参照元と参照先でドキュメントの記述形式が一致している必要はありません。

interface Base {
/** A method. */
void m()
}

class Derived implements Base {
/// {@inheritDoc}
public void m() { }
}

今後追加される可能性がある拡張

特定のコンテンツが含まれる定型的な見出しを検出し Javadoc タグに変換する機能が、サポートされる可能性があります。

たとえば、Parameters という見出しの後に、パラメータ名とその説明のリストが続く場合、同等の @param タグに変換できます。

▼ 変換前

# Parameters

- x the x coordinate
- y the y coordinate

▼ 変換後

@param x   the x coordinate
@param y the y coordinate

同じく例外のリストについても同様の方針を採用できます。

▼ 変換前

# Throws

* NullPointerException if the first parameter is `null`
* NullPointerException if the second parameter is `null`
* IllegalArgumentException if an argument is not accepted

▼ 変換後

@throws NullPointerException      if the first parameter is `null`
@throws NullPointerException if the second parameter is `null`
@throws IllegalArgumentException if an argument is not accepted

ただし、両者を比較してみるとわかりますが、マークダウン形式で記述する方が Javadoc タグで記述する場合より行数が増えています。
JEP の中でも、開発者は Javadoc タグを使用した従来の形式で完結に記述する方を好む可能性があると言及されており、本拡張が実現するかどうかは開発者コミュニティからのフィードバックや実際の利用状況に大きく依存すると考えられます。

The proposed forms do look like normal Markdown, but they also take up more vertical space. Developers may prefer to stay with the more concise forms, using old-style JavaDoc tags.

Javadoc タグのシンプルさや長年の慣習による使い勝手の良さが評価される一方で、よりリッチな表現力を持つ Markdown の導入により、複雑なドキュメント作成やメンテナンスが効率化される可能性もあります。最終的には、両者のバランスをどのように取るかが、今後の Java 開発者にとって重要な判断ポイントとなるでしょう。

JEP 474: ZGC: Generational Mode by Default

https://openjdk.org/jeps/474

ZGC においてデフォルトモードが世代別 GC になりました。

ZGC は、Java 21(JEP479: Generational ZGC)にて世代別 GC がサポートされ、それが今回のアップデートにてデフォルトになった形です。

そもそも ZGC とは何か

ZGC は、Oracle が開発したガベージコレクタ(GC)で、スケーラブルで低レイテンシ(数テラバイト級の非常に大きいヒープでもアプリケーションの最大停止時間が 10ms 程度)というのが特徴です。

Java 9 以降、G1GC がデフォルトの GC として使われていますが、巨大なヒープを取り扱うアプリケーションが登場してきたことなどを背景に、従来の GC と比べてモダンな GC として登場しました。(cf. ざっくりわかった気になるモダン GC 入門

Java 11(JEP333)で試験的に導入され、Java 15(JEP377)にて正式リリースされ、今に至ります。

世代別 GC とは何か

世代別 GC 自体は特に新しい概念ではありません。世代別 GC とは、ヒープ内のオブジェクトを寿命によって分類(Young 世代や Old 世代)し、ガベージコレクション(GC)の効率を向上させるしくみです。(cf. Java の GC の仕組みを整理する

ZGC は、低遅延であることを最優先に、ヒープ全体のコレクションを極力並行処理で行うことを目的として、当初は世代別 GC のアプローチを採用しませんでした。
そこから、より効率的なメモリ管理などさらなる最適化を目指して、世代別 GC の概念を取り入れていったという形になります。

JEP 471: Deprecate the Memory-Access Methods in sun.misc.Unsafe for Removal

https://openjdk.org/jeps/471

sun.misc.Unsafe のメモリアクセスメソッドが非推奨になりました。代替として VarHandle API (JEP 193) および Foreign Function & Memory API (JEP 454) への移行が推奨されています。

sun.misc.Unsafe は通常の Java API では制御できないような、JVM やメモリに対する危険で強力な操作を実施するための API を提供します。この手の話を聞くとリフレクションを思い浮かべる方もいるかもしれませんが、リフレクションよりもはるかに強力な黒魔術となります。(cf. sun.misc.Unsafe の魔力

もともとは、JVM のガベージ コレクションされたヒープ内、または JVM によって制御されないオフヒープメモリ内のメモリにアクセスすることを主目的とした内部用の API として導入されました。しかしながら、標準 API よりも高いパワーとパフォーマンスを求めるライブラリ開発者にとっては便利で魅力的な API であり、Spring や Mockito といったメジャーなフレームワークやライブラをはじめとして広く使われる結果になったという経緯があります。

現在では本節の冒頭でも説明した、代替となる安全でパフォーマンスの高い 2 つの標準 API が提供されているため、そちらの利用が推奨されています。

ソースレベルでの移行例については、JEP の Migration Examples を参照してください。

おわりに

JDK 23 のアップデートでは、言語機能やライブラリに直接関連する正式なアップデートはないですが、調べてみるとどのアップデートも重要なアップデートだとわかります。

今回初めて JEP を真剣に読んでみたのですが、非常によくまとまっていて、最新の仕様とその経緯を理解するには非常に良いドキュメントだなと思いました。