3 instanceof演算子のパターン・マッチング
パターン・マッチングでは、オブジェクトに特定の構造があるかどうかをテストし、一致がある場合には、そのオブジェクトからデータを抽出します。これはJavaでも実行できることですが、パターン・マッチングには新しい言語拡張が導入されており、より簡潔で強力なコードを使用してオブジェクトから条件付きでデータを抽出できます。
より具体的には、JDK 14で、instanceof
演算子が拡張されており、バインディング変数を指定できます。instanceof
演算子の結果がtrue
の場合、テスト対象のオブジェクトは、バインディング変数に割り当てられます。
注意:
これは、設計、仕様および実装は完了しているが、永続的でないプレビュー機能であり、将来のJDKリリースでは別の形式で存在するか、完全になくなる可能性があることを意味します。プレビュー機能が含まれているコードをコンパイルして実行するには、追加のコマンド行オプションを指定する必要があります。「プレビュー機能」を参照してください。
instaceof
演算子のパターン・マッチングに関する背景情報は、JEP 305を参照してください。
特定の形状の周辺の長さを計算する次のコードを見ていきます:
public interface Shape { }
final 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; }
}
public static double getPerimeter(Shape shape) throws IllegalArgumentException {
if (shape instanceof Rectangle) {
Rectangle s = (Rectangle) shape;
return 2 * s.length() + 2 * s.width();
} else if (shape instanceof Circle) {
Circle s = (Circle) shape;
return 2 * s.radius() * Math.PI;
} else {
throw new IllegalArgumentException("Unrecognized shape");
}
}
メソッドgetPerimeter
は、次を実行します:
Shape
オブジェクトのタイプを判断するためのテストinstanceof
演算子の結果に応じて、Shape
オブジェクトをRectangle
またはCircle
にキャストする変換Shape
オブジェクトから長さと幅または半径を抽出することによる分解
パターン・マッチングを使用すると、instanceof
演算子の2番目のオペランドがタイプ・テスト・パターンで変更され、変換のステップがなくなるため、短くて読みやすいコードになります。
public static double getPerimeter(Shape shape) throws IllegalArgumentException {
if (shape instanceof Rectangle s) {
return 2 * s.length() + 2 * s.width();
} else if (shape instanceof Circle s) {
return 2 * s.radius() * Math.PI;
} else {
throw new IllegalArgumentException("Unrecognized shape");
}
}
注意:
この変換のステップがなくなることで、コードもより安全になります。instanceof
でオブジェクトのタイプをテストし、キャストを使用してそのオブジェクトを新しい変数に割り当てると、アプリケーションでコーディング・エラーが発生することがあります。あるオブジェクト(テスト対象のオブジェクトまたは新しい変数のいずれか)のタイプを変更し、その他のオブジェクトのタイプの変更を忘れてしまう場合があります。
パターンは、ターゲットに適用可能な述語と、述語が一致した場合にのみターゲットから抽出される一連のバインディング変数の組合せです。述語は、ある引数のブール値を持つ関数です。この場合は、Shape
引数がRectangle
であるかCircle
であるかをテストするinstanceof
演算子が述語です。ターゲットは述語の引数で、Shape
引数がそれに当たります。バインディング変数は、述語がtrue
を返す場合にのみターゲットのデータを格納する変数で、変数s
がそれに当たります。
タイプ・テスト・パターンは、タイプを指定する述語と、単一のバインディング変数で構成されます。この例では、タイプ・テスト・パターンはRectangle s
とCircle s
です。
バインディング変数のスコープ
バインディング変数のスコープは、instanceof
演算子がtrue
の場合にのみプログラムが到達できる場所です:
public static double getPerimeter(Shape shape) throws IllegalArgumentException {
if (shape instanceof Rectangle s) {
// You can use the binding variable s (of type Rectangle) here.
} else if (shape instanceof Circle s) {
// You can use the binding variable s of type Circle here
// but not the binding variable s of type Rectangle.
} else {
// You cannot use either binding variable here.
}
}
バインディング変数のスコープは、それが導入された文を超えて拡張できます。
public static boolean bigEnoughRect(Shape s) {
if (!(s instanceof Rectangle r)) {
// You cannot use the binding variable r here.
return false;
}
// You can use r here.
return r.length() > 5;
}
バインディング変数は、if
文の式で使用できます:
if (shape instanceof Rectangle s && s.length() > 5) {
// ...
}
条件付きAND演算子(&&
)は短絡演算子であるため、プログラムがs.length() > 5
式に到達できるのは、instanceof
演算子がtrue
の場合のみです。
逆に、次のような場合には、instanceof
演算子でパターン・マッチングを実行することはできません。
if (shape instanceof Rectangle s || s.length() > 0) { // error
// ...
}
instanceof
がfalseの場合にプログラムがs.length() || 5
に到達できるため、ここではバインディング変数s
は使用できません。