はじめに
instanceof
を使う時って、↓のようにキャストすることあるじゃないですか。
if (obj instanceof String) {
String s = (String) obj;
// do anything
}
これがJava 16からキャスト不要だったらしいです。
「そんなん知ってるわ」って方は以下読む必要ないです。
こうなる
instanceof
のパターンマッチングと言います。
最初のコードは↓のように書けます。
if (obj instanceof String s) {
// do anything
}
この1行で↓が行われます。
-
obj
がString
だった場合はString
にキャスト - 変数
s
に代入(ここではs
のことをパターン変数と呼びます)
よって、後続の処理ではs
を使っていろいろと処理が書けるわけです。
パターン変数のスコープ
パターン変数のスコープは、以下のように述べられています。
A pattern variable is only in scope where the compiler can deduce that the pattern has definitely matched and the variable will have been assigned a value.
「パターン変数は、パターンが確実にマッチしていて、その変数に値が代入されているとコンパイラが推測できる場合にのみスコープに入る。」
つまり、、、パターン変数のスコープは変数に値が代入されて意味を持つときだけとなります。(これをflow scoping
といいます、多分)
例えば、以下のようになります。
public class Sample {
void foo(Object obj) {
// (1)
if (obj instanceof String a) {
// objがString型の場合、パターン変数aのスコープ内
}
// (2)
if (obj instanceof Boolean a) {
// objがBoolean型の場合、パターン変数aのスコープ内
}
// パターン変数aのスコープ外
}
このコードでは(1)
と(2)
で同じパターン変数a
を使っています。
instanceof
で型と一致した場合はそれぞれのif
ブロック内にa
はスコープを持ち、マッチしない場合はスコープがないです。
!
を使ったらスコープは逆になります。
public class Sample {
void foo(Object obj) {
if (!(obj instanceof String s)) {
// objはString型ではなくsのスコープ外
} else {
// objはString型でsのスコープ内
}
// sのスコープ外
}
}
もう1つ!
のサンプルをば。
public class Sample {
void foo(Object obj) {
if (!(obj instanceof String s)) {
throw new IllegalArgumentException("Must be a string");
}
// sのスコープ
var s = "hoge"; // コンパイルエラー
}
}
if
文が終わった後はスコープ変数s
がString
型を持つことが保証されているので、if
の後がs
のスコープになります。
なので、s
を再度宣言しようとするとコンパイルエラーになります。
また、例外をスローしていますが、例外をスローしない場合は、s
がString
型をもつことが保証できないため、スコープ外となり宣言できます。
ちなみに
フィールド変数をshadowing
(重複するスコープで同じ名前の変数を使って、低レベルスコープの変数が高レベルスコープの変数をオーバライドすること)できます。
class Example {
String s;
void test(Object o) {
if (o instanceof String s) {
System.out.println(s); // shadowing
s = s + "\n";
}
System.out.println(s);
}
}
これを以下のコードで実行するとhoge
、null
と表示されます。
public class Main {
public static void main(String[] args) {
Example e = new Example();
e.test("hoge");
}
}
使い道
例えば、Java 16より前だとequals
で↓のようなコードを書くこともあったと思います。
public class Foo {
int i;
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Foo)) {
return false;
}
// instanceofで確認した後にキャスト
Foo other = (Foo) obj;
if (other.i != this.i) {
return false;
}
return true;
}
}
Foo
オブジェクトの変数i
が等しい場合にtrue
を返すequals
メソッドです。
↑がこう↓書けます。
public class Foo {
int i;
@Override
public boolean equals(Object obj) {
return (obj instanceof Foo other) && other.i == this.i;
}
}
おわりに
まちがっていたり、補足があればご指摘いただけるとうれしいです。