レコード・パターン

レコード・パターンを使用して、値がレコード・クラス・タイプのインスタンスであるかどうかをテストし(レコード・クラスを参照)、該当する場合はそのコンポーネント値に対してパターン・マッチングを再帰的に実行できます。

レコード・パターンの背景情報は、JEP 440を参照してください。

次の例では、objが、レコード・パターンPoint(double x, double y)Pointレコードのインスタンスかどうかをテストします:

record Point(double x, double y) {}
// ...

static void printAngleFromXAxis(Object obj) {
    if (obj instanceof Point(double x, double y)) {
        System.out.println(Math.toDegrees(Math.atan2(y, x)));
    }
}     

また、この例ではx値とy値をobjから直接抽出し、Pointレコードのアクセサ・メソッドを自動的にコールします。

レコード・パターンは、型とレコード・パターン・リスト(空の場合あり)で構成されます。この例で、型はPointで、パターン・リストは(double x, double y)です。

ノート:

null値はどのレコード・パターンとも一致しません。

次の例は、レコード・パターンではなくタイプ・パターンを使用する点を除き、前の例と同じです:

static void printAngleFromXAxisTypePattern(Object obj) {
    if (obj instanceof Point p) {
        System.out.println(Math.toDegrees(Math.atan2(p.y(), p.x())));
    }
}   

汎用レコード・パターン

レコード・クラスが汎用の場合、レコード・パターンに明示的に型引数を指定できます。たとえば:

record Box<T>(T t) { }
// ...

static void printBoxContents(Box<String> bo) {
    if (bo instanceof Box<String>(String s)) {
        System.out.println("Box contains: " + s);
    }
}

値をパターンのレコード・タイプにキャストできるのであれば、値がパラメータ化されたレコード・タイプのインスタンスであるかどうかをテストでき、未チェック変換は必要ありません。次の例ではコンパイルが行われません:

static void uncheckedConversion(Box bo) {
    // error: Box cannot be safely cast to Box<String>
    if (bo instanceof Box<String>(var s)) {
        System.out.println("String " + s);
    }
}    

レコード・パターンでのvarの使用

レコード・パターンのコンポーネント・リストでvarを使用できます。コンパイラは、パターンを受け入れるすべての構文(switch文、switch式、instanceof式)で、レコード・パターンの型引数の型を推論できます。

次の例では、コンパイラによって、パターン変数xおよびydouble型であることが示されます:

static void printAngleFromXAxis(Object obj) {
    if (obj instanceof Point(var x, var y)) {
        System.out.println(Math.toDegrees(Math.atan2(y, x)));
    }
}     

次の例は、printBoxContentsと等価です。コンパイラは型引数とパターン変数を推論します。Box(var s)Box<String>(String s)と推論されます

static void printBoxContentsAgain(Box<String> bo) {
    if (bo instanceof Box(var s)) {
        System.out.println("Box contains: " + s);
    }
} 

次の例で、コンパイラはMyPair(var s, var i)MyPair<String, Integer>(String s, Integer i)と推論します:

record MyPair<T, U>(T x, U y) { }
// ...

static void recordInference(MyPair<String, Integer> p) {
    switch (p) {
        case MyPair(var s, var i) -> 
            System.out.println(s + ", #" + i);
    }
}

レコード・パターンでのプリミティブ型

レコードのコンポーネントの型がプリミティブの場合でも、それらの型に対してパターン・マッチングを実行できます。

ノート:

レコードのコンポーネントのプリミティブ型に対するパターン・マッチングのサポートは、プレビュー機能です。プレビュー機能は、設計、仕様および実装が完了したが、永続的でない機能です。プレビュー機能は、将来のJava SEリリースで、異なる形式で存在することもあれば、まったく存在しないこともあります。プレビュー機能が含まれているコードをコンパイルして実行するには、追加のコマンド行オプションを指定する必要があります。

詳細は、JEP 488: パターン、instanceofおよびswitchでのプリミティブ型(第2プレビュー)を参照してください。

次の例では、レコードCupdouble型のコンポーネントが1つ含まれています。この例では、floatを含むCupを作成し、Cupdoublefloatまたはint値が含まれているかどうかを、instanceof演算子を使用してテストします:

record Cup(double d) { }
//...

Cup myCup = new Cup(34.567f);

if (myCup instanceof Cup(double d)) System.out.println("Cup contains double: " + d);
if (myCup instanceof Cup(float f)) System.out.println("Cup contains float: " + f);
if (myCup instanceof Cup(int i)) System.out.println("Cup contains int: " + i);

次のような出力が表示されます:

Cup contains double: 34.56700134277344
Cup contains float: 34.567

同様に、「プリミティブ型を持つ型パターン」で説明したように、レコードのコンポーネントの値は、型パターンの型に値をキャストすることが安全である場合、対応する型パターンと一致します。キャストまたは変換中に情報が失われたり、例外がスローされたりしない場合、キャストは安全です。たとえば、floatdoubleまたはfloatにキャストすることは安全です。floatintにキャストすることは安全ではありません。(この例で変換されたdouble値が元のfloat値と異なる理由については、DoubleのJavaDoc APIドキュメントの10進数と2進数の間の変換の問題の項を参照してください。)

ネストされたレコード・パターン

レコード・パターンを別のレコード・パターン内にネストできます:

enum Color { RED, GREEN, BLUE }
record ColoredPoint(Point p, Color c) {}
record ColoredRectangle(ColoredPoint upperLeft, ColoredPoint lowerRight) {}   
// ...

static void printXCoordOfUpperLeftPointWithPatterns(ColoredRectangle r) {
    if (r instanceof ColoredRectangle(
        ColoredPoint(Point(var x, var y), var upperLeftColor),
                     var lowerRightCorner)) {
        System.out.println("Upper-left corner: " + x);
    }
}   

パラメータ化されたレコードでも同じことをできます。コンパイラは、レコード・パターンの型引数とパターン変数の型を推論します。次の例で、コンパイラはBox(Box(var s))Box<Box<String>>(Box(String s))と推論します。

static void nestedBox(Box<Box<String>> bo) {
    // Box(Box(var s)) is inferred to be Box<Box<String>>(Box(var s))
    if (bo instanceof Box(Box(var s))) {    
        System.out.println("String " + s);
    }
}