instanceof演算子のパターン・マッチング
パターン・マッチングでは、オブジェクトに特定の構造があるかどうかをテストし、一致がある場合には、そのオブジェクトからデータを抽出します。これはJavaでも実行できることですが、パターン・マッチングには新しい言語拡張が導入されており、より簡潔で強力なコードを使用してオブジェクトから条件付きでデータを抽出できます。
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
は、次を実行します:
Shape
オブジェクトのタイプを判断するためのテストinstanceof
演算子の結果に応じて、Shape
オブジェクトをRectangle
またはCircle
にキャストする変換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
でオブジェクトのタイプをテストし、キャストを使用してそのオブジェクトを新しい変数に割り当てると、アプリケーションでコーディング・エラーが発生することがあります。あるオブジェクト(テスト対象のオブジェクトまたは新しい変数のいずれか)のタイプを変更し、その他のオブジェクトのタイプの変更を忘れてしまう場合があります。
パターンは、述語と呼ばれるテスト、ターゲット、およびパターン変数と呼ばれる一連のローカル変数の組合せですgetPerimeter
の例には、s instanceof Rectangle r
とs instanceof Circle c
の2つのパターンが含まれています:
- 述語は、引数が1つのboolean値関数です。これら2つのパターンで、これは
instanceof
演算子で、s
引数がRectangle
とCircle
のどちらかをテストします。 - ターゲットは述語の引数です。これら2つのパターンで、これは
s
値です。 - パターン変数は、述語が
true
の場合のみ、ターゲットのデータを格納するものです。これら2つのパターンで、これらはr
とc
です。
タイプ・パターンは、タイプを指定する述語と、単一のパターン変数で構成されます。この例では、タイプ・パターンはRectangle r
およびCircle c
です。
パターン変数のスコープ
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
は使用できません。
パターン変数を使用できるその他の例は、「パターン変数宣言のスコープ」を参照してください。