WisdomSoft - for your serial experiences.

5.9 instanceof演算子

オブジェクトを指定した型に変換できるかどうかは instanceof 演算子で調べられます。

5.9.1 確実なナローイング変換

あらゆる参照型を Object 型にワイドニング変換することについてプログラム的なリスクは存在しません。しかし、一度スーパークラスやインタフェース型に変換したオブジェクトを、元の型に戻す場合はリスクが存在します。もし、変換対象のオブジェクトが、指定した型のインスタンスを保有していなければ、実行時例外となってしまいます。これは、コンパイル時に判断することができません。

そこで、変数が参照しているインスタンスが指定した型に変換可能であるかどうかを知る方法が必要になります。こればかりはコンパイル時に決定することができないため、実行時に instanceof 演算子を用いてチェックする必要があります。instance 演算子は2項関係演算子に分類されます。

instance 演算子
関係式 instanceof 参照型

左オペランドの関係式には参照型か null 型以外を指定することはできません。右オペランドには、左オペランドがキャスト可能かどうかを調べる対象の参照型識別子を指定します。右オペランドに変数やリテラルを指定することはできません。

instanceof 演算子は、左オペランドのオブジェクトが、右オペランドで指定した型にキャスト可能であれば true、そうでなければ false を返します。instanceof 演算子が true を返したならば、そのオブジェクトは指定した型に安全にキャストすることができることを保証できます。逆に false を返した場合、オブジェクトを指定した参照型にキャストしようとすると、実行時例外が確実に発生します。

コード1
interface I {}
class A {}
class B extends A {}
class C extends B implements I {}

class Test implements I {
	public static void main(String args[]) {
		check(new C());
		check(new B());
		check(new Test());
	}
	public static void check(Object obj) {
		System.out.println("----" + obj.toString() + "----");
		System.out.println("instanceof I = " + (obj instanceof I));
		System.out.println("instanceof A = " + (obj instanceof A));
		System.out.println("instanceof B = " + (obj instanceof B));
		System.out.println("instanceof C = " + (obj instanceof C));
		System.out.println("----------------------\n");
	}
}
実行結果
>java Test
----C@5ff48b----
instanceof I = true
instanceof A = true
instanceof B = true
instanceof C = true
----------------------

----B@affc70----
instanceof I = false
instanceof A = true
instanceof B = true
instanceof C = false
----------------------

----Test@e63e3d----
instanceof I = true
instanceof A = false
instanceof B = false
instanceof C = false
----------------------

コード1は、I インタフェースと、継承関係にあるクラス A、B、C を宣言しています。Test クラスの check() メソッドでは、Object 型の参照を受け取り、これが I インタフェース及び A、B、C クラスにキャスト可能かどうかを調べ、その結果を表示します。結果は見ての通りです。

C クラスのインスタンスは、A スーパークラスと、B 直接のスーパークラスを持つため、A 型、B 型にキャスト可能であり、I インタフェースも実装しているため I 型にもキャスト可能です。B クラスのインスタンスは、C クラスの情報は持っていないため C 型にキャストすることはできません。また、I インタフェースも実装していないので、I 型にもキャストすることはできません。最後の Test クラスのインスタンスは、A クラスの系列とは何の関係もないため A、B、C 型のどれにも変換することはできません。しかし、I インタフェースは実装しているため、I 型に変換することはできます。

instanceof 演算子を用いるタイミングは、オブジェクトが指定した型を実装していないという事実が、問題にはなるものの致命的ではない場合です。もし、オブジェクトが指定した型にキャストできないという結果が、プログラムの動作にとって致命的な場合、または、プログラムの設定が確実に誤っていると想定される場合は、instanceof を使ってキャストを避けるのではなく、キャスト演算子を用いて確実に例外を発生させるべきなのです。instanceof によって過ちが隠蔽されてしまうと、プログラマは誤ったオブジェクトを設定している事実に、なかなか気づくことができないかもしれないためです。

5.9.2 オブジェクトの比較

instanceof 演算子の実用例を示すために、オブジェクトの比較を行うプログラムを作成しようと思います。実は、== 演算子を用いて参照型を比較した結果は、右オペランドと左オペランドのオブジェクトが同じインスタンスを指しているかどうかを返します。例えば、次のような場合の結果は true となります。

String str1 = "Kitty on your lap";
String str2 = str1;
System.out.println(str1 == str2);

しかし、変数が参照しているインスタンスが同一であるかどうかという比較は、人間にとって直観的ではありません。文字列の比較であれば、同じ文字並びであるか、座標クラスの比較であれば、同じ座標を指しているかという比較の方が重要です。この問題は、次のような比較が false となることで確認できます。

 String str1 = "Kitty
on your lap"; String str2 = "" + str1;
System.out.println(str1 == str2);
System.out.println("str1 = " + str1 + "\nstr2 = " + str2);

str1 と str2 の文字並びはまったく同じですが、== 演算子の結果は false となります。これは、str1 と str2 が異なるインスタンスを参照しているためです。とすれば、プログラマはオブジェクトに格納されている情報が同じかどうかを調べるために、クラスが提供するフィールド情報をすべて比較しなければならなくなります。これは現実的ではありません。

そこで、座標やサイズ、色、文字列など、より直観的なオブジェクトの比較が求められるデータは、Object クラスの equals() メソッドをオーバーライドします。equals() メソッドは、オブジェクトが指定したオブジェクトと論理的に等しいかどうかを調べるためのメソッドであると定義されています。このメソッドをクラスの目的によってオーバーライドすれば、オブジェクト同士の比較が簡単になるのです。equals() メソッドは次のような宣言になります。

public boolean equals(Object obj);

equals() メソッドは、引数から渡されたオブジェクトが自分自身と論理的に等しいかどうかを調べ、等しければ true を返します。問題は、仮パラメータから Object 型のオブジェクトを受け取るということです。論理的な情報の比較を行うには、obj 変数を、equals() をオーバーライドしたクラスの型に変換しなければなりません。しかも、メソッドの仕様上、渡されたオブジェクトが異なる型のものであれば、例外ではなく false を返すべきだと考えられるため、実行時例外を発生させるわけにはいきません。そこで、instanceof 演算子の登場となるのです。

コード2
class Point {
	public final int x , y;
	public Point(int x , int y) {
		this.x = x;
		this.y = y;
	}
	public boolean equals(Object obj) {
		if (!(obj instanceof Point)) {
			System.out.println(toString() + " not " + obj.toString());
			return false;
		}

		Point pt = (Point)obj;
		System.out.println(pt.x + " = " + this.x + " & " + pt.y + " = " + this.y);
		return ((pt.x == this.x) && (pt.y == this.y));
	}
}

class Test {
	public static void main(String args[]) {
		Object obj = new Point(400 , 300);
		System.out.println(obj.equals(new Point(300 , 300)) + "\n");
		System.out.println(obj.equals(new Test()) + "\n");
		System.out.println(obj.equals(new Point(400 , 300)));
	}
}
実行結果
>java Test
300 = 400 & 300 = 300
false

Point@c78e57 not Test@5224ee
false

400 = 400 & 300 = 300
true

コード2は、2次元座標を表す Point クラスを作成しています。2次元座標を表す 2 つの Point オブジェクトの x フィールドと y フィールドの値がそれぞれ等しければ、オブジェクトが論理的に等しいことを証明できるでしょう。これを行うには Object クラスの equals() メソッドをオーバーライドし、渡されたオブジェクトを調べる必要があります。

フィールドを比較して調べるには Object 型を Point 型にキャストする必要があります。しかし、ナローイング変換の場合は確実にキャスト変換できるとは限りません。そのため instanceof 演算子を用いてオブジェクトが Point 型にキャスト可能かどうかを調べ、そうでなければ、渡されたオブジェクトは確実に等しくならないため false を返します。