5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

JavaAdvent Calendar 2023

Day 6

【Java】instanceofした後のキャストが要らない件(Java 16)

Last updated at Posted at 2023-12-06

はじめに

instanceofを使う時って、↓のようにキャストすることあるじゃないですか。

if (obj instanceof String) {
    
    String s = (String) obj;
    
    // do anything
}

これがJava 16からキャスト不要だったらしいです。

「そんなん知ってるわ」って方は以下読む必要ないです。

こうなる

instanceofのパターンマッチングと言います。

最初のコードは↓のように書けます。

if (obj instanceof String s) {
    
    // do anything
}

この1行で↓が行われます。

  1. objStringだった場合はStringにキャスト
  2. 変数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文が終わった後はスコープ変数sString型を持つことが保証されているので、ifの後がsのスコープになります。
なので、sを再度宣言しようとするとコンパイルエラーになります。

また、例外をスローしていますが、例外をスローしない場合は、sString型をもつことが保証できないため、スコープ外となり宣言できます。

ちなみに

フィールド変数を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);
	}
}

これを以下のコードで実行するとhogenullと表示されます。

Main.java
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;
	}
}

おわりに

まちがっていたり、補足があればご指摘いただけるとうれしいです。

5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?