JEP 440, レコードパターンは、JDK 21のためにProposedからTargetedに昇格された。このJEPは、この機能を最終化し、過去2回のプレビューからのフィードバックに対応した機能拡張を組み込んでいる。JEP 432Record Patterns (Second Preview)(JDK 20で提供)、JEP 405Record Patterns (Preview)(JDK 19で提供)だ。この機能は、レコード値を分解するためのレコードパターンを持つ言語を強化するものだ。
レコードパターンは、タイプパターンと組み合わせて使用することで、強力で宣言的、かつ複合的なデータナビゲーションと処理の形態を可能にする 。タイプパターンは、最近、スイッチケースラベルで使用するために拡張された。JEP 420, Pattern Matching for switch (Second Preview), in JDK 18, and JEP 406, Pattern Matching for switch (Preview), in JDK 17. JEP 432からのもっとも大きな変更点は、強化されたfor
文のヘッダーに表示されるレコードパターンのサポートを削除したことだ。
これらの変化により、Javaはネスト可能なレコードパターンの導入により、より宣言的でデータに重点を置いたプログラミングスタイルへの道を歩むことになった。この進化は、Java 16でJEP 394とともに導入されたinstanceof
演算子によるパターン・マッチの統合が成功した後のことだ。
Point
というレコードとColor
という列挙型がある状況を考えてみよう。
record Point(int x, int y) {}
enum Color { RED, GREEN, BLUE }
新しい レコードパターンでは、オブジェクトがrecord
のインスタンスであるかどうかをテストし、そのコンポーネントを直接分解できる。例えば、以下のような感じだ。
if (r instanceof Rectangle(ColoredPoint ul, ColoredPoint lr)) {
System.out.println(ul.c());
}
さらに強力なのは、record
値をさらに分解できるネストされたパターンを使用できることだ。次のような宣言を考えてみよう。
record ColoredPoint(Point p, Color c) {}
record Rectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {}
左上の点から色を抽出したい場合は、次のように書くことができる。
if (r instanceof Rectangle(ColoredPoint(Point p, Color c), ColoredPoint lr)) {
System.out.println(c);
}
このレコードパターンの進化は、パターンマッチングを レコードクラスのインスタンスの分解に拡張し、より高度なデータクエリを可能にする。オブジェクトが レコードのインスタンスであるかどうかをテストし、オブジェクトのコンポーネントを直接的な抽出をできる。このアプローチにより、コードがより簡潔になり、エラーが発生しにくくなる。次のような例を考えてみよう。
static void printXCoordOfUpperLeftPointWithPatterns(Rectangle r) {
if (r instanceof Rectangle(ColoredPoint(Point(var x, var y), var c),
var lr)) {
System.out.println("Upper-left corner: " + x);
}
}
さらに、ネストされたパターンの導入により、ネストされたデータ構造を分解する機能が追加された。パターン全体がマッチするかどうかにかかわらず、エラー処理を一元化ができる。これにより、個々のサブパターンのマッチング失敗をチェックし処理する必要がなくなる。
これらのネストされたパターンは、JEP 441で導入されたswitch
式ともうまく連携している。switch式のパターン・マッチは、switch
文を拡張し、ケース・ラベルにパターンを使用できるようにする。これにより、より表現力豊かなコードになり、switch
文のケースを見逃すことによるバグの可能性が低くなる。
例えば、宣言を考えてみよう。
class A {}
class B extends A {}
sealed interface I permits C, D {}
final class C implements I {}
final class D implements I {}
record Pair<T>(T x, T y) {}
Pair<I> p;
記録パターンと網羅的なswitch
で、以下のことができる。
switch (p) {
case Pair<I>(C c, I i) -> ...
case Pair<I>(D d, C c) -> ...
case Pair<I>(D d1, D d2) -> ...
}
しかし、これらのアップデートには、いくつかのリスクと前提がある。あらゆる言語の変更と同様に、既存のコードベースに影響を与えるリスクがある。さらに、これらの変更は、開発者がJavaの比較的新しい機能であるレコードクラスとパターンマッチに精通していることを前提としている。
今後、レコードパターンを拡張していく方向性はたくさんある。例えば、可変引数のレコードのためのvarargsパターン、任意の値にマッチするがパターン変数を宣言しない名前のないパターン、レコードクラスだけでなく任意のクラスの値にも適用できるパターンなどである。
結論として、Javaに レコードとネストされたパターンを導入することは、この言語にとって大きな飛躍となる。より宣言的なコーディング・スタイルが可能になり、よりクリーンで理解しやすいコードを実現ができる。多少のリスクはあるものの、潜在的なメリットを考えると、今後のJavaのバージョンアップに期待したい機能である。