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 406を参照してください。

次の例では、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式および文のパターン・マッチング」を参照してください

ガード付きパターンおよびカッコ付きパターン

ノート:

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

ガード付きパターンでは、ブール式でパターンを絞り込むことができます。値がガード付きパターンに一致するのは、値がパターンに一致し、かつ、ブール式が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は、ガード付きパターンを使用してcaseラベルに移動できます:

    static void test(Object obj) {
        switch (obj) {
            case String s && (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 && eで、pはパターン、eはブール式です。pに表示されるパターン変数のスコープには、eが含まれます。これにより、長さが1より大きいStringにキャストできる値に一致するString s && (s.length() > 1)などのパターンを指定できます。

カッコ付きパターン

ノート:

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

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

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