レコード・パターン

ノート:

これはプレビュー機能です。プレビュー機能は、設計、仕様および実装が完了したが、永続的でない機能です。プレビュー機能は、将来のJava SEリリースで、異なる形式で存在することもあれば、まったく存在しないこともあります。プレビュー機能が含まれているコードをコンパイルして実行するには、追加のコマンド行オプションを指定する必要があります。『Preview Language and VM Features』を参照してください。

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

レコード・パターンを使用して、値がレコード・クラス・タイプのインスタンスであるかどうかをテストし(レコード・クラスを参照)、該当する場合はそのコンポーネント値に対してパターン・マッチングを再帰的に実行できます。次の例では、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を使用できます。次の例では、コンパイラによって、パターン変数xおよびydouble型であることが示されます:

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

コンパイラは、パターンを受け入れるすべての構文(switch文および式、instanceof式、拡張for文)で、レコード・パターンの型引数の型を推論できます。

次の例は、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);
        }
    } 

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

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

    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);
        }
    }

拡張for文

レコード・パターンは、拡張for文に指定できます:

record Pair<T>(T x, T y) {}

    static void printPairArray(Pair[] pa) {
        for (Pair(var first, var second) : pa) {
            System.out.println("(" + first + ", " + second + ")");
        }
    }

レコード・パターンを含む拡張for文でnull値が検出されると、MatchExceptionがスローされます。次の例では、printPairArrayの拡張for文で、mypanull値が検出されると、MatchExceptionがスローされます。

        Pair[] mypa = new Pair[]{ 
            new Pair<Integer>(1,2),
            null,            
            new Pair<String>("hello","world")
        };
        
        printPairArray(mypa);