はじめに
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;
}
}
おわりに
まちがっていたり、補足があればご指摘いただけるとうれしいです。