new するたびに別物、をやめる。JEP 401が示すJavaの新しい選択肢

new するたびに別物、をやめる。JEP 401が示すJavaの新しい選択肢 プログラミング

海外のJavaコミュニティで、JEP 401に関する投稿が伸びていました。
「Value ObjectsがJDK 28を対象として正式に提案された」という内容です。

投稿そのものは、リンクと一行だけ。
それでも、多くの支持を集めました。

そして、コメント欄には実務家らしい補足が並んでいます。
本記事は、その投稿と寄せられたコメントを読んで、内容を整理したものです。

「proposed to target」は何を意味するのか

見出しに出てくる proposed to target という言葉。

そのまま読むと「もう決まった」に見えます。
しかし、実際はもう少し手前の段階です。

コメント欄では、こんな流れが説明されていました。
JEPが特定のリリースを狙う段階に入ると、まず proposed to target という札がつきます。

そこから、一定の待機期間があります。
そして、異論が出なければ targeted へ昇格。

開発チームは、この時点でようやくコードをメインラインへ統合できます。
統合が終わると、ステータスは integrated に変わります。
実際のビルドに姿を現すのは、そのあとの最初のビルドから。

だから、あるコメントの指摘も悪い知らせではありません。
「JDK 28のb08にはまだ入っていない」という内容でした。

理由は、課題管理システム上の「Resolved in Build」欄が空だったから。
段階を知っていれば、想定内の状態だと分かります。

値オブジェクトは何を変えるのか

Javaでは、これまですべてのオブジェクトが固有のアイデンティティを持っていました。
new を呼ぶたびに、別物が生まれます。

だから、中身がまったく同じでも == は false を返す。
Integer や LocalDate で一度は面食らった経験があるでしょう。

JEP 401は、この前提に選択肢を持ち込みます。
クラス宣言に value 修飾子をつけてください。
すると、そのクラスのインスタンスはアイデンティティを持ちません。

value record Color(byte red, byte green, byte blue) { }

宣言はこれだけ。
あとは、普通のクラスと同じように書けます。

ただし、制約が2つあります。

  • インスタンスフィールドは暗黙にfinalになる
  • インスタンスメソッドにsynchronizedは書けない

そして、== の意味が変わります。
同じクラスで、フィールドの値がすべて一致しているとしましょう。

この場合、いつどこで生成されたかに関係なく「同じ」と判定されます。
intの4が常に4であるのと同じ感覚。
そう考えると、腑に落ちるはずです。

なお、比較についてはひとつ注意点があります。
== が見るのは、オブジェクトの内部状態だけ。

一方、クラスの作者が「同じデータを表す」と考える基準は、多くの場合 equals に書くべきものです。
JEPの説明でも、普段の比較には equals を使うよう勧めています。

性能面のうまみ

アイデンティティを手放すと、JVM側の自由度が一気に上がります。
JEPが挙げている代表格は、スカラー化とフラット化の2つ。

スカラー化は、値オブジェクトへの参照をフィールドの値そのものへ展開する技法です。
メソッドの引数やローカル変数が、個々の値に置き換わります。
その結果、ヒープ割り当てが消えます。

従来の逃避解析でも、似たことは起きていました。
ただし、効く条件が読みにくいという弱点があります。

しかも、オブジェクトがスコープの外へ逃げると効かなくなる。
値オブジェクトなら、この最適化がずっと予測しやすくなります。

フラット化のほうは、発想が違います。
配列や他オブジェクトのフィールドに、参照ではなくビット列を直接埋め込むのです。

Color[] が実質的に int[] のように扱われる。
そう考えると、イメージしやすいでしょう。

メモリ使用量が減ります。
さらに、キャッシュミスも減ります。

もっとも、万能ではありません。
フラット化されたデータは、原子的に読み書きできる大きさに収まる必要があります。

一般的なプラットフォームでは、64ビット程度が上限。
だから、intを2つ持つクラスやdoubleを1つ持つクラスは、通常のヒープオブジェクトとして扱われることもあります。
このあたりは、実装の裁量です。

まだプレビューという事実

コメント欄に、こんな質問がありました。
「これは最終形なのか、それともプレビューなのか」。

返ってきた答えは、最初のプレビュー。
ですから、試すには –enable-preview が要ります。

また、プレビューを有効にすると、標準ライブラリの一部が値クラスとして振る舞います。
Integer などのラッパークラスが、これに該当。

その結果、ボクシングのコストが目に見えて下がると説明されています。
逆に、プレビューを切ってコンパイルした場合は従来どおりの挙動のままです。

そして、プレビュー機能の構文や振る舞いは変わり得ます。
フィードバック次第で、リリースごとに調整されるからです。

だから、本番前提で設計を固めるのは、まだ早い。
JDK 28の公開自体、2027年3月の予定です。

Vector APIへの波及

コメントで一番「なるほど」と思ったのが、Vector APIに関する指摘でした。

Vector APIは、JDK 16からインキュベーターとして提供されています。
そして、12回目の再インキュベーションがJDK 27に入りました。

12回です。
なぜ、ここまで引っ張るのか。

理由は、はっきりしています。
Valhallaの機能がプレビューとして使えるようになるまで待つ。
そうJEPが自ら明言しているからです。

ベクトルのクラスを、値クラスとして宣言したい。
それが叶って初めて、インキュベーターからプレビューへ進みます。

つまり、JEP 401がJDK 28でプレビューに入るとしましょう。すると、長らく満たせなかった前提条件がようやく揃うわけです。コメント欄が沸いていたのも納得でした。

まとめ

JEP 401が、JDK 28に向けてproposed to targetになりました。
ただし、これは最初のプレビュー。

正式機能への道のりは、これからです。
それでも、注目に値する変化だと感じます。

アイデンティティは、これまですべてのオブジェクトが払う固定費でした。
それが、必要なときだけ選ぶものへ変わります。

この転換は、書くコードそのものより大きな意味を持つでしょう。
JVMが裏で何をできるか、その幅が広がるからです。

いま手を動かすなら、自分のコードから値クラスの候補を探してみてください。
小さな不変のデータを表すクラス。

レコード。そして、== に依存していない箇所。
そういう部分なら、修飾子をひとつ足すだけで移行できる可能性があります。

この記事のポイント

  • JEP 401はJDK 28を対象に提案された段階で、targetedや統合はこの先
  • value 修飾子をつけたクラスはアイデンティティを持たず、== はフィールド値で比較される
  • スカラー化とフラット化により、ヒープ割り当てとキャッシュミスが減る
  • 最初のプレビューなので –enable-preview が必要で、仕様は今後変わり得る
  • 12回インキュベートされたVector APIは、この機能を待って昇格する方針

参考にした情報源は、2つあります。
Redditに投稿されたJEP 401関連のスレッド。
そして、そこで言及されていたOpenJDKの公式JEPページです。

タイトルとURLをコピーしました