原文はこちら。
The original article was written by Nicolai Parlog (Java Developer Advocate, Oracle).
Quality Outreach Heads-up – JDK 28: Value Classes – Inside.java
OpenJDK Quality Groupは、リリースの全体的な品質向上の手段としてOpenJDKビルドを使ってのFOSSプロジェクトのテストを推進しています。
Quality Outreach
https://wiki.openjdk.java.net/display/quality/Quality+Outreach
このお知らせは、関係するプロジェクトに送られるQuality Outreachの最新情報の一部です。
quality-discuss
https://mail.openjdk.org/archives/list/quality-discuss@openjdk.org
このプログラムの詳細と参加方法については、上記wikiをご覧ください。
JEP 401では、JDK 28のプレビュー機能としてValueクラスが導入されています。この追加機能は互換性に通常とは異なる影響を及ぼすため、さらなるテストが有益です。本記事では、まず2つのテスト手法について概要を説明した後、JEPが互換性に及ぼす影響についてより詳細に解説します。
JEP 401: Value Objects (Preview)
Testing with JDK 28
新機能はプレビューとして導入されるため、プレビュー機能が無効になっている状態(デフォルト設定)での JDK の動作は、従来通りであるべきですが、可能性は低いものの、変更の範囲が広いため、プレビュー機能を無効にした状態での動作が意図せず影響を受けている可能性は排除できません。JDK 28 に対してテストを行うプロジェクトは、予期しない動作がないか注意深く確認してください。
プレビュー機能を有効にした場合、30のJDKクラスがValueクラスになります。特に注目すべきは、IntegerやDoubleといったプリミティブラッパークラスです。これにより、既存のコードは変更されていないにもかかわらず新機能を使用し始めるという、珍しい互換性の影響が生じ、より広範囲にわたる動作の変化につながる可能性があります。これらは以下の3つのカテゴリに分類されます。
- 「通常の」クラスとValueクラス間の予想される動作の違い — プロジェクトでは、これらの変更に対応するためにコードを変更する必要があるかもしれません。
- Recordコンストラクタの予想される動作変更 — プロジェクトでは、これらの変更に対応するためにコードを変更する必要があるかもしれません。
- JDKの予期せぬ誤動作 — これはvalhalla-devメーリングリスト
Info | valhalla-dev@openjdk.org – openjdk.org
に報告してください。
したがって、十分に柔軟な継続的インテグレーション環境を備えたプロジェクトについては、プレビュー機能を無効化した状態および有効化した状態で、JDK 28 に対してコードベースをビルドおよびテストすることを提案します。
Expected Behavior Changes
Value Classes
プレビュー機能有効化済みのJDK 28上でコードを実行する場合、以下の動作の変更が適用されます。これは、コードがプレビュー機能を無効化した状態でコンパイルされた場合や、JDK 28 以前のバージョンでコンパイルされた場合でも同様です。
以下の30個のJDKクラスはValueクラスです。
- package
java.lang:Integer,Long,Float,Double,Byte,Short,Character,BooleanNumber,Record
- package
java.util:Optional,OptionalInt,OptionalLong,OptionalDouble
- package
java.time:LocalDate,LocalTime,LocalDateTime,ZonedDateTime,OffsetTime,OffsetDateTimeDuration,Instant,Period,Year,YearMonth,MonthDay
- package
java.time.chrono:MinguoDate,HijrahDate,JapaneseDate,ThaiBuddhistDate
Valueクラスにはidentityがありません。identityとは、それ以外では区別できない2つのインスタンスを見分けるために使用できる特性です。
var x = LocalDate.of(2026, 7, 31);
var y = LocalDate.of(2026, 7, 31);
var identical = x == y;
// the result depends on value classes:
// * without value classes, `identical` is `false`
// * with value classes, `identical` is `true`
これは同一性を重視する操作に影響を与えます。
==はフィールド値によってValueオブジェクトを比較するため、JDKのValueクラスのインスタンスの場合、==は以前とは異なり(上記の例のように)trueを返すことがある- Valueオブジェクトにはオブジェクトモニターがないため、同期できず、waitやnotifyといったメソッドはサポートされていない。モニターとのやり取りはコンパイルエラーまたは
IdentityExceptionを引き起こす java.lang.refAPIおよびjava.util.WeakHashMapはValueオブジェクトでは使用できず、IdentityExceptionをスローする- Valueオブジェクトは真に不変であり、そのフィールドに対して
setAccessibleを呼び出すと、IllegalAccessExceptionが発生する。これは、finalフィールドの変更が有効になっている場合でも同様である
他にも制限はありますが、それらはカスタムValueクラスにのみ適用されるものであり、本記事の範囲外です。
互換性の観点から、JDK 8で値ベースクラスという概念が導入されたことは注目に値します。
これは当時のValueクラスの制限を概説したもので、Optionalクラスやjava.time.*パッケージ内のクラスも包含していました。それ以来、説明は洗練されてきました(例えば、同期処理が明示的に言及されるようになった)ほか、JDK 16ではプリミティブのラッパークラスも含まれるように拡張されています。
Record Constructors
JDK 28でのコンパイル時にプレビュー機能が有効になっている場合、既存のRecordコンストラクタは追加のルールに従う必要があり、これにより新たなコンパイルエラーが発生する可能性があります。プレビュー機能なしでコンパイルし、プレビュー機能有効な状態で実行された非準拠のコンストラクタは、コンパイルされた通りに実行されます。
具体的には、Recordコンストラクタのコードはデフォルトでearly constructionフェーズ中に実行されます。このフェーズでは、他の制限に加え、thisとの相互作用がフィールドへの代入に限定されます(early constructionの詳細と背景については、JEP 513: Flexible Constructor Bodiesを参照)。その結果、プレビュー機能を有効にしてコンパイルすると、thisと相互作用するステートメント(例:インスタンスメソッドの呼び出し)を含むレコードコンストラクタでエラーが発生します。
既存のコンパクトコンストラクタには、構築中のインスタンスを、すべてのフィールドがnull、0などに設定された状態で他のコードに公開するため、そのような文は含まれていないはずです。もし含まれている場合(かつその文を単純に削除できない場合)、コンストラクタを非コンパクトなバリエーションにリファクタリングする必要があります。非コンパクトなコンストラクタにそのような文が含まれている場合、以下のように修正してください。
- 問題のある文を削除する、もしくは、
- リファクタリングして、early constructionコンテキスト(静的コンテキストと非常に類似)で動作するようにする、もしくは、
- すべてのフィールドに値を代入した後、
super()またはthis(...)(これによりearly constructionが終了します)を呼び出し、その後、以前問題となっていた文を実行する(これにより、構築中のインスタンスが外部に公開されなくなります)
Call to Action
プロジェクトをJDK 28でビルドおよびテストし、可能であればプレビュー機能を有効にしたJDK 28でもテストしてください。フィードバックはvalhalla-devメーリングリスト(登録が必要)からお願いします。
Info | valhalla-dev@openjdk.org – openjdk.org
この変更案の詳細および上記の互換性への影響については、JEP 401を参照してください。
コメントをどうぞ