4 パターン・マッチング

パターン・マッチングでは、オブジェクトに特定の構造があるかどうかをテストし、一致がある場合には、そのオブジェクトからデータを抽出します。これはJavaでも実行できることです。ただし、パターン・マッチングには新しい言語拡張が導入されており、より簡潔で強力なコードを使用してオブジェクトから条件付きでデータを抽出できます。

instanceofのパターン・マッチング

次の例では、パラメータshapeRectangleまたはCircleである場合のみ、その周辺の長さを計算します:
interface Shape { }
record Rectangle(double length, double width) implements Shape { }
record Circle(double radius) implements Shape { }
...
    public static double getPerimeter(Shape shape) throws IllegalArgumentException {
        if (shape instanceof Rectangle r) {
            return 2 * r.length() + 2 * r.width();
        } else if (shape instanceof Circle c) {
            return 2 * c.radius() * Math.PI;
        } else {
            throw new IllegalArgumentException("Unrecognized shape");
        }
    }

パターンは、述語と呼ばれるテスト、ターゲット、およびパターン変数と呼ばれる一連のローカル変数の組合せです:

  • 述語は、ある引数のブール値を持つ関数です。この場合は、Shape引数がRectangleであるかCircleであるかをテストするinstanceof演算子が述語です。
  • ターゲットは述語の引数で、Shape値がこれに当たります。
  • パターン変数は、述語がtrueを返した場合のみ、ターゲットからのデータを格納する変数です。これらは、変数rおよびsです。

詳細は、instanceof演算子のパターン・マッチングを参照してください。

switchのパターン・マッチング

ノート:

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

switch式および文のパターン・マッチングに関する背景情報は、JEP 427を参照してください。

次の例では、RectangleまたはCircleのインスタンスに対してのみ、周辺の長さを計算します。ただし、if-then-else文のかわりにswitch式が使用されます:

    public static double getPerimeter(Shape shape) throws IllegalArgumentException {
        return switch (shape) {
            case Rectangle r -> 2 * r.length() + 2 * r.width();
            case Circle c    -> 2 * c.radius() * Math.PI;
            default          -> throw new IllegalArgumentException("Unrecognized shape");
        }
    }

「switch式および文のパターン・マッチング」を参照してください

when句

ノート:

この機能は、プレビュー機能であるJEP 427の一部です。

when句では、ブール式でパターンを絞り込むことができます。when句を含むパターン・ラベルはガード付きパターン・ラベルと呼ばれ、when句のブール式はガードと呼ばれます。値がガード付きパターン・ラベルと一致するのは、値がパターンと一致し、かつ、ブール式がtrueと評価される場合です。次に例を示します。

    static void test(Object obj) {
        switch (obj) {
            case String s:
                if (s.length() == 1) {
                    System.out.println("Short: " + s);
                } else {
                    System.out.println(s);
                }
                break;
            default:
                System.out.println("Not a string");
        }
    }

ブール式s.length == 1は、when句を使用するとcaseラベルに移行できます:

    static void test(Object obj) {
        switch (obj) {
            case String s when s.length() == 1 -> System.out.println("Short: " + s);
            case String s                      -> System.out.println(s);
            default                            -> System.out.println("Not a string");
        }
    }

最初のパターン・ラベル(ガイド付きパターン・ラベル)が一致するのは、objStringでその長さが1の場合です。2番目のパターン・ラベルが一致するのはobjが異なる長さのStringである場合です。

ガード付きパターン・ラベルの形式は、p when eで、pはパターン、eはブール式です。pで宣言されるパターン変数のスコープには、eが含まれます。

カッコ付きパターン

ノート:

この機能は、プレビュー機能であるJEP 427の一部です。

カッコ付きパターンは、カッコで囲まれたパターンです。ガード付きパターンはパターンと式を組み合せるため、解析のあいまいさが生じる可能性があります。パターンをカッコで囲むことで、これらのあいまいさを回避したり、パターンを含む式を異なる方法で解析するようにコンパイラに強制したり、コードの可読性を高めることができます。次に例を示します。

    static Function<Integer, String> testParen(Object obj) {
        boolean b = true;
        return switch (obj) {
            case String s && b -> t -> s;
            default            -> t -> "Default string";
        };
    }    

この例はコンパイルされます。ただし、最初の矢印トークン(->)がcaseラベルの一部であり、ラムダ式の一部ではないことを明確にしたい場合は、ガード付きパターンをカッコで囲むことができます:

    static Function<Integer, String> testParen(Object obj) {
        boolean b = true;
        return switch (obj) {
            case (String s && b) -> t -> s;
            default              -> t -> "Default string";
        };
    }

レコード・パターン

ノート:

この機能は、プレビュー機能であるJEP 405の一部です。

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

    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レコードのアクセサ・メソッドを自動的にコールします。次の例は、レコード・パターンではなくタイプ・パターンを使用する点を除き、前の例と同じです:

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

レコード・パターンのコンポーネント・リストで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)));
        }
    }     

レコード・パターンは、タイプ、レコード・コンポーネント・パターン・リスト(空の可能性あり)、およびオプションの識別子で構成されます。識別子を含むレコード・パターンは名前付きレコード・パターンと呼ばれ、変数はレコード・パターン変数です。次に示すのは、名前付きレコード・パターンの例です(rがレコード・パターン変数):

    record Rectangle(Point upperLeft, Point lowerRight) {
        void printDimensions() {
            System.out.println(
                "Length = " + Math.abs(lowerRight.x - upperLeft.x) +
                ", height = " + Math.abs(lowerRight.y - upperLeft.y));
        }
    }

    static void printRectangleDimensions(Object obj) {
        if (obj instanceof Rectangle(var ul, var lr) r) {
            System.out.printf("Upper-left corner: (%.2f, %.2f)%n", ul.x, ul.y);
            System.out.printf("Lower-right corner: (%.2f, %.2f)%n", lr.x, lr.y);
            r.printDimensions();
        }
    }

現在、レコード・パターンには制限があります。汎用レコード・クラスを指定する場合、パターンではRAW型ではなく汎用型を使用する必要があります。次の例では、コンパイルが行われます:

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

ただし、次の例ではコンパイルが行われません:

    record Box<T>(T t) { }

    static void erroneousPrintBoxContents(Box<Object> bo) {
        // Error: Raw deconstruction patterns are not allowed
        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);
        }
    }   

ノート:

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