JEP 394: Pattern Matching for instanceof
インスタンスの型を判断して処理を行う instanceof-and-cast イディオム以下のようになります。
if (obj instanceof String) { String s = (String) obj; System.out.println(s); }
instanceof パターンマッチングでは、以下のように書くことができるようになります。
if (obj instanceof String s) { System.out.println(s); }
パターン変数 s
がキャスト不要で利用可能になります。
例えば以下のようなよくある equals
メソッドがあった場合、
public boolean equals(Object o) { if (!(o instanceof Point)) return false; Point other = (Point) o; return x == other.x && y == other.y; }
instanceof パターンマッチングにより以下のように簡素に記載することができます。
public boolean equals(Object o) { return (o instanceof Point other) && x == other.x && y == other.y; }
パターン変数のスコープ
パターン変数は、一種のローカル変数と考えることができます。 しかしローカル変数とはスコープの解釈で異なる点があります。
これを一言で表せば、
パターン変数のスコープは、それが確実に一致する範囲内になる
ということになります。 具体的に見てみましょう。
以下の例ではパターン変数のスコープが有効で問題ありません。
if (obj instanceof String s && s.length() > 5) { ... }
しかし以下の例では、パターン変数のスコープ外でコンパイルエラーとなります。
if (obj instanceof String s || s.length() > 5) { // Error! ... }
もう少し例を見ると、以下の例は問題ありません。
public void onlyForStrings(Object o) throws MyException { if (!(o instanceof String s)) throw new MyException(); System.out.println(s); // s is in scope ... }
println に到達するのは、パターンマッチングが成功した場合に限られるため、パターン変数 s
のスコープには、メソッドブロックの条件ステートメントに続くステートメントが安全に含まれます。
つまり「パターン変数のスコープは、それが確実に一致する範囲内になる」ということになります。
パターン変数によるシャドーイング
パターン変数は、フィールド宣言をシャドーイングできます。
フィールド変数の p
と同じ名前でパターン変数を定義した場合は以下のようになります。
class Example2 { Point p; void test2(Object o) { if (o instanceof Point p) { // パターン変数 p が有効 } else { // フィールド変数 p が有効(パターン変数 p は無効) } } }
シャドーイングで注意が必要なケースがあります。
以下の例です。
class Example1 { String s; void test1(Object o) { if (o instanceof String s) { System.out.println(s); // パターン変数 p が有効 s = s + "\n"; // パターン変数 p への割り当て } System.out.println(s); // フィールド変数 p を参照 } }
以下のようなケースでは、最後の println の時点では、パターン変数 s
はスコープ外となり、フィールド変数を参照することに注意が必要です。
まとめ
instanceof パターンマッチングにより醜いキャストの削減が可能となりました。
もうこのようなコードを書く必要はありません。
public boolean equals(Object o) { return (o instanceof CaseInsensitiveString) && ((CaseInsensitiveString) o).s.equalsIgnoreCase(s); }
JEP 394 自体の変更は小さなものですが、switch 式でのパターンマッチにつながる内容となります。
Java17 では以下のようにパターン変数を利用することができる予定です。
String formatted = switch (o) { case Integer i -> String.format("int %d", i); case Long l -> String.format("long %d", l); case Double d -> String.format("double %f", d); case String s -> String.format("String %s", s); default -> o.toString(); };