instanceofによるパターン・マッチング

instanceofによるパターン・マッチングでは、ターゲットの型がパターンの型と一致するかどうかをテストします。一致する場合、ターゲットはパターンの型に変換され、パターン変数はターゲットのデータで自動的に初期化されます。

instanceof演算子のパターン・マッチングに関する背景情報は、JEP 394を参照してください。

特定の形状の周辺の長さを計算する次のコードを見ていきます:

public interface Shape {
    public static double getPerimeter(Shape s) throws IllegalArgumentException {
        if (s instanceof Rectangle) {
            Rectangle r = (Rectangle) s;
            return 2 * r.length() + 2 * r.width();
        } else if (s instanceof Circle) {
            Circle c = (Circle) s;
            return 2 * c.radius() * Math.PI;
        } else {
            throw new IllegalArgumentException("Unrecognized shape");
        }
    }
}

public class Rectangle implements Shape {
    final double length;
    final double width;    
    public Rectangle(double length, double width) {
        this.length = length;
        this.width = width;
    }    
    double length() { return length; }
    double width() { return width; }
}

public class Circle implements Shape {
    final double radius;
    public Circle(double radius) {
        this.radius = radius;
    }  
    double radius() { return radius; }
}

メソッドgetPerimeterは、次を実行します:

  1. Shapeオブジェクトのタイプを判断するためのテスト
  2. instanceof演算子の結果に応じて、ShapeオブジェクトをRectangleまたはCircleにキャストする変換
  3. Shapeオブジェクトから長さと幅または半径を抽出することによる分解

パターン・マッチングを使用すると、instanceof演算子の2番目のオペランドがタイプ・パターンで変更され、変換のステップがなくなるため、短くて読みやすいコードになります:

public static double getPerimeter(Shape shape) throws IllegalArgumentException {
    if (s instanceof Rectangle r) {
        return 2 * r.length() + 2 * r.width();
    } else if (s instanceof Circle c) {
        return 2 * c.radius() * Math.PI;
    } else {
        throw new IllegalArgumentException("Unrecognized shape");
    }
}

ノート:

この変換のステップがなくなることで、コードもより安全になります。instanceofでオブジェクトのタイプをテストし、キャストを使用してそのオブジェクトを新しい変数に割り当てると、アプリケーションでコーディング・エラーが発生することがあります。あるオブジェクト(テスト対象のオブジェクトまたは新しい変数のいずれか)のタイプを変更し、その他のオブジェクトのタイプの変更を忘れてしまう場合があります。詳細は、「instanceofおよびswitchによる安全なキャスト」を参照してください。

この例では、2つのパターンを使用します:

  • Rectangle r
  • Circle c

パターンRectangle rは、instanceof式のオペランドです。ターゲットsの型がパターンで指定したもの(Rectangle)かどうかをテストします。同様に、式s instanceof Circle cは、sの型がCircleかどうかをテストします。

値がパターンに正常に一致した場合、パターン変数はターゲットのデータで初期化されます。この例では、ターゲットsRectangleの場合、sRectangleに変換され、rに割り当てられます。同様に、sCircleの場合はCircleに変換され、cに割り当てられます。

パターン変数のスコープとinstanceof

パターン変数のスコープは、instanceof演算子がtrueの場合にのみプログラムが到達できる箇所です:
public static double getPerimeter(Shape shape) throws IllegalArgumentException {
    if (shape instanceof Rectangle s) {
        // You can use the pattern variable s (of type Rectangle) here.
    } else if (shape instanceof Circle s) {
        // You can use the pattern variable s of type Circle here
        // but not the pattern variable s of type Rectangle.
    } else {
        // You cannot use either pattern variable here.
    }
}

パターン変数のスコープは、それを導入した文を超えて拡張できます:

public static boolean bigEnoughRect(Shape s) {
    if (!(s instanceof Rectangle r)) {
        // You cannot use the pattern variable r here because
        // the predicate s instanceof Rectangle is false.
        return false;
    }
    // You can use r here.
    return r.length() > 5; 
}

パターン変数は、if文の式で使用できます:

if (shape instanceof Rectangle r && r.length() > 5) {
    // ...
}

条件付きAND演算子(&&)は短絡演算子であるため、プログラムがr.length() > 5式に到達できるのは、instanceof演算子がtrueの場合のみです。

逆に、次のような場合には、instanceof演算子でパターン・マッチングを実行することはできません。

if (shape instanceof Rectangle r || r.length() > 0) { // error
    // ...
}

instanceofがfalseの場合、プログラムはr.length() || 5に到達できるため、ここではパターン変数rは使用できません。

パターン変数を使用できるその他の例は、「パターン変数のスコープとswitch」を参照してください。