Javaについて徹底解説!

Javaで型を判断するinstanceof 基本や考え方、応用までズバリ解説!

大石 英人

大石 英人

開発エンジニア/Java20年/Java GOLD/リーダー/ボールド歴2年

Javaの演算子instanceofを使うと、変数が指すインスタンスが何のクラスかがわかります。

instanceofは使ったことがない人も多いでしょう。変数の型を見れば何のクラスはすぐにわかりますし、そのような判断をする意味はなさそうにも見えます。

でも、Javaはクラスの継承が使えるオブジェクト指向プログラミング言語だからこそ、instanceofのような機能が必要なのです。

この記事では、そんなinstanceofについて、使い方や活用方法を初心者向けにお伝えします。

※この記事のサンプルは、Java 10の環境で動作確認しています。


1.instanceofはインスタンスの実体を判断する

1-1.instanceofの構文

演算子instanceofを使うと、変数が指すインスタンスが、指定したクラス・インターフェイスか判断できます。

instanceofの構文は以下のとおりです。

変数名 instanceof クラス名

instanceofの戻り値はbooleanなので、trueなら指定したクラスやインターフェイスである、falseならそうではない、ということです。

1-2.instanceofの例

では、さっそくinstanceofを使ってみます。

例として、Javaで整数をクラスとして扱う時に出てくる、java.lang.Integerを使ってみます。

Integer i = Integer.valueOf(1);

boolean iof = i instanceof Integer;
System.out.println(iof); // → true

まあ、そりゃあそうなるよね、という結果です。型がIntegerの変数に「あなたはIntegerですか」と聞いたら、trueとしかならないでしょう。

これだけだと、instanceofが何のためにあるのか分かりません。

instanceofは、静的型付けが特徴のオブジェクト指向プログラミング言語であるJavaの、「静的」という制限をかいくぐったり、クラスの継承関係を正しく把握するために使うものです。

その例を、次の章でお伝えします。


2.instanceofの意味のある使い方

2-1.Integerの継承関係と実装インターフェイスのおさらい

この章ではIntegerを例に使いますので、Integerのクラスの継承関係と実装インターフェイスをおさらいします。

Integerは以下の継承関係にあるのはご存知だと思います。Integerと同じ深さの継承の階層に、LongDoubleなどの他の数値を表すクラスがいくつかありますよね。

java.lang.Object

                ┗java.lang.Number

                        ┗java.lang.Integer

                        ┗java.lang.Long

                        ┗java.lang.Double

さらに、Integerはjava.lang.Comparableと、java.io.Serializableを実装したクラスです。

ですのでIntegerは、Integer/Object/Number/Comparable/Serializableの、全部で5つの顔を持ったクラスだということです。

2-2.サブクラスを知る

instanceofの使い道の一つは、変数の型に継承関係がある時に、実際はどのサブクラスなのか知ることです。

ここで「IntegerNumberである」と言えます。Integerには先に述べたクラスの継承関係があるからです。これは英語だと「Integer is a Number」と書けて、こういう関係を「IS-A関係」とも呼びます。

逆に「NumberIntegerである(Number is a Integer)」とは100%言い切れません。なぜかというと、Numberが指す変数の先にあるのは、LongDoubleかもしれないからです。

これらを頭に入れてもらった上で、以下のプログラムを読んでみてください。

Number n1 = Integer.valueOf(1);
Number n2 = Long.valueOf(1);
Number n3 = Double.valueOf(1);

System.out.println(n1 instanceof Integer); // → true、Numberの実体はInteger!!
System.out.println(n2 instanceof Integer); // → false、Numberの実体はIntegeではない(Long)
System.out.println(n3 instanceof Integer); // → false、Numberの実体はIntegeではない(Double)

変数の型は全部Numberです。そして、実体はInteger/Long/Doubleなので全部違います。ですから、全てNumberとして扱えていますが、実体が何であるかは変数の型からは分かりません。

このように、変数が指すインスタンスの本当のクラスが何であるかを知るために、instanceofを使うのです。

ちなみに、instanceofではスーパークラスの確認もできます。

Integer i = Integer.valueOf(1);

System.out.println(i instanceof Number); // → trueだけれど、これは当たり前
System.out.println(i instanceof Object); // → trueになるのも当たり前

これはコンパイルエラーにはなりませんが、うれしいことも大してありません。

クラスの継承関係上で、IntegerNumberObjectだと、最初から分かり切っているからです。

2-3.インターフェイスの実装クラスを知る

instanceofは、型がインターフェイスの場合、その実装クラスが何かを調べる時にも使います。

Comparableは、Javaの中でも一番といっていいくらい広く実装されているインターフェイスです。IntegerはComparableを実装しているので、「IntegerはComparableである」と言えますよね。皆さんがよく使うStringもComparableを実装しているので、「StringはComparableである」とも言えます。

ですから、NumberStringも、Comparableというインターフェイスとして同一視できます。Comparableな変数が指すインスタンスのクラスは、全く異なるものであり得るということです。

こういうややこしい状況を、instanceofを使えば以下のように判断できるのです。

Comparable c1 = Integer.valueOf(1);
Comparable c2 = "String";

System.out.println(c1 instanceof Number); // → true
System.out.println(c1 instanceof String); // → false

System.out.println(c2 instanceof Number); // → false
System.out.println(c2 instanceof String); // → true

つまり、インターフェイスの型を使うことで見えなくなっている、変数の先にあるインスタンスの実体クラスが何であるか、判断できています。

2-4.クラスが実装しているインターフェイスを知る

さらにinstanceofでは、型がインターフェイスの場合に限り、そのインターフェイスの実体クラスが、別のどのインターフェイスを実装しているのかを調べられます。

Comparable c = Integer.valueOf(1);

System.out.println(c instanceof Serializable); // → true
System.out.println(c instanceof AutoCloseable); // → false

Integerは前述のとおり、Comparableの他に、java.io.Serializableも実装しています。でも、java.lang.AutoCloseableは実装していません。

AutoCloseableはComparableとは継承関係にない、他人とも言えるインターフェイスです。でも、Comparableな変数の先にあるインスタンスは、AutoCloseableを実装しているかも知れませんよね。

つまり、インターフェイスが型である変数は、その変数の先にあるインスタンスがどのインターフェイスを実装しているかわからないのです。

そんな時にinstanceofを使えば、インターフェイスがどの別のインターフェイスを実装しているか判断できます。

2-5.Objectの実体を知る

変数の型がObjectである場合は、ある意味で特別なケースだと言えます。Objectは全てのクラスのスーパークラスなので、変数が指す実体は、全てのクラス・インターフェイスのどれでもありうるからです。

ですから、Objectを型とする変数は、どのクラスやインターフェイスともinstanceofで確認ができます。

Object o = Integer.valueOf(1);

System.out.println(o instanceof Object); // → true
System.out.println(o instanceof Number); // → true
System.out.println(o instanceof Integer); // → true
System.out.println(o instanceof Long); // → false
System.out.println(o instanceof Double); // → false
System.out.println(o instanceof String); // → false
System.out.println(o instanceof Serializable); // → true
System.out.println(o instanceof Comparable); // → true
System.out.println(o instanceof AutoCloseable); // → false

3.instanceofのいろいろ

3-1.instanceofとキャスト

instanceofとほとんどセットになるのが、変数を別の型へキャストすることです。キャストして大丈夫かどうかをinstanceofで確認をして、実行時エラーを出さないようにします。

例えば、以下のようなものがよく見られるものですね。if文でチェックしないとClassCastExceptionが発生し、プログラムが途中で止まってしまうかもしれないからです。

Object o = Integer.valueOf(1);
Integer i = null;
String s = null;

if (o instanceof Integer) {
	i = (Integer)o;
} else if (o instanceof String) {
	s = (String)o;
}

なお、この例では変数の型としてObjectを使いましたが、クラスを対象にした場合で、ダウンキャスト(サブクラスへのキャスト)ができるかどうかを確認するのにも使います。

3-1-1.Object型変数の功罪

例えば、フレームワーク上ではObject型の変数を扱うことで、事実上どんなクラス・インターフェイスでも扱えるようにしていることがあります。ですが、扱っているものがどのクラスかインターフェイスであるか確認したいことももちろんあり、そこがinstanceofの出番です。

これが顕著だったのが、例えばJava 1.4以前のCollectionフレームワークです。ListやSetObjectを格納し、MapObjectをキーとしObjectを値としていました。ですから、どんなクラスでも扱えるフレームワークではありましたが、使う時は必ずキャストを強いられたのです。

Java 1.5でのジェネリクス(Generics、型引数)の導入でinstanceofが原則不要になったので、Collectionフレームワークは大変使いやすく、かつ安全になりました。

でも、まだObjectを使っているフレームワークはありますし、別の用途もありますので、instanceofを使うことはなくならないでしょう。

3-2.instanceofで否定する場合

instanceofで「これは~である」が分かりますが、逆の「これは~ではない」は直接は分かりません。

ですので、instanceofの結果を否定したいなら、否定演算子!を「!(instanceofの結果)」の形で使います。ちなみに「X !instanceof Y」という形ではありませんので注意しましょう。

Object o = Integer.valueOf(1);

if (!(o instanceof Integer)) {
	System.out.println("Integerではありません");
} else if (!(o instanceof String)) {
	System.out.println("Stringではありません");
}

3-3.instanceofとswitch

instanceofを使っていると、if-elseが付き物です。ifの条件にいちいちinstanceofを書くのは面倒なので、switchですっきり書けないかなと誰しも考えます。

ですが、残念ながらswitchClassを値に使えないので、以下のプログラムはコンパイルエラーです。

Object o = Integer.valueOf(1);

switch (o.class) {
case Integer:
	// oをIntegerにキャストして何かする
	break;
case String:
	// oをStringにキャストして何かする
	break;
defalult:
	break;
}

もしかすると、後述するClass.isInstanceやisAssignableFromを使うと、多少は上手く書けるかもしれません。ですが実際は、if文とinstanceofで素直に書いた方が早いですし、トラブルや試行錯誤も少なくて済むと思います。

将来のJavaで何かしらの仕様が追加されることもあまり期待はできなさそうですが、たくさん連続するif文を見ると、何かのデザインパターンやEnumを使ってきれいに書けないかなと考えたくなるのは分かります。

3-4.instanceofと配列

instanceofでは、変数が指すインスタンスが、何かのクラス・インターフェイスの配列かどうかも調べられます。配列かどうかを調べるには、instanceofの右辺値を配列型とするだけです。

以下の例で、配列とそうでないものもinstanceofで区別されているのが分かると思います。

Object obj = new String[] { "A", "B", "C" }; // ①
Object[] objArr = new Integer[] { Integer.valueOf(1), Integer.valueOf(2), Integer.valueOf(3) }; // ②

System.out.println(obj instanceof String[]); // true
System.out.println(obj instanceof String); // false

System.out.println(objArr instanceof Integer[]); // true
System.out.println(objArr instanceof Number[]); // true
System.out.println(objArr instanceof Long[]); // false
// System.out.println(objArr instanceof Integer); // コンパイルエラー!!

この例ではの部分、すなわち配列をObject型で宣言した変数に代入しているのは、あまり見ないと思います。

Javaの配列は参照型の値です。配列は「配列型のインスタンス」とでも表現すべきものであり、どんな型でも取り得るObjectにそのまま代入できるのです。ですので、Objectが配列かどうかを調べているこの例は、コンパイルエラーにはなりません。

3-5.instanceofとジェネリクス

instanceofの右辺値は、クラス名を直接書かなければならないので、ジェネリクスでの型引数は使えません。

例えば、以下のようにはできず、コンパイルエラーとなります。

class InstanceofGenerics<T> {
	void instanceofTest(Object o) {
		System.out.println(o instanceof T); // instanceofでコンパイルエラー!!
	}
}

なぜかというと、この例で言う型引数のTが、このクラスのコンパイル時には何であるかわからないからです。ジェネリクスの型引数が指すクラスは、実はクラスファイルの内部的には全てObject扱いとなっています。

そのため「o instanceof Object」となるのですが、これはnull以外なら常にtrueとなり、事実上何の意味もないどころかバグとも考えられかねないので、明示的にコンパイルエラーにしているのだと思われます。

3-6.nullはどんなクラスでもない

変数が指すのがnullならinstanceofの結果は必ずfalseです。例えどんなクラス・インターフェイスを指定してもです。

これは、instanceofは変数の型をチェックするものではなく、変数が指している先のインスタンスをチェックするものだからです。nullなら実際のクラスすらわからないのですから、チェックのしようがないですよね。

Number n = null;

if (n instanceof Integer) {
	System.out.println("Integerです");
} else {
	System.out.println("Integerではありません"); // if文での判定結果はこちらになる
}

3-7.instanceofがコンパイルエラーになるケース

instanceofでは、クラスやインターフェイスの継承関係上で明らかに実行結果がfalseになるケースは、コンパイルエラーになります。

どうしてもinstanceofのところでコンパイルエラーになってしまうなら、クラスやインターフェイスの継承関係をチェックしてみましょう。

例えば、IntegerStringはクラスの継承関係上で共通しているのは、Objectであることだけです。ですので、以下のIntegerStringか確認しているプログラムは、instanceofでコンパイルエラーになります。

IntegerはStringではないことが、Javaのコンパイラには明らかだからです。

Integer i = Integer.valueOf(1);
System.out.println(i instanceof String);

抽象クラスやインターフェイスは、それが最終的にどのように実装されるかわからないものなので、instanceofでの制限が緩いのだと言えるでしょう。


4.【参考】Class.isInstance/isAssignableFromでの動的判断

instanceofはJavaの組み込み構文です。instanceofを使う時は右辺値にクラス名を書くので、クラス名を知らなければ使えないということです。

でもプログラムを作っていると、クラス名が分からなくても実際のインスタンスが何か判断したいことがあります。

これはフレームワークなどの実際のクラス名が分からないケースでよく出てきます。

4-1.Class.isInstanceで動的にinstanceofする

Class.isInstanceを使うと、何のクラス・インターフェイスであるかが動的に分かります。

isInstanceはメソッドなので、instanceofでの右辺値となるクラス名を、引数で動的に指定できるのです。

public boolean isInstance(Object obj)
パラメータ:
	obj - チェック対象のオブジェクト
戻り値:
	objがこのクラスのインスタンスの場合はtrue
// メソッドの引数でインスタンスとクラスを受け取り、動的に判断する
class IsInstanceTest {
	static void isInstance(Object o, Class clazz) {
		System.out.println(clazz.isInstance(o));
	}

	public static void main(String[] args) {
		isInstance(Integer.valueOf(1), Integer.class); // → true
		isInstance(Integer.valueOf(1), Number.class); // → true
		isInstance(Integer.valueOf(1), Long.class); // → false
		isInstance(Integer.valueOf(1), String.class); // → false
		isInstance(Integer.valueOf(1), Comparable.class); // → true
		isInstance(Integer.valueOf(1), AutoCloseable.class); // → false
		isInstance(null, Integer.class); // → false
	}
}

なお、Class.isInstanceは、instanceofとは構文上の主客が逆なのに注意しましょう。

instanceofは、変数が指すインスタンスが与えられたクラス・インターフェイスかを確認します。

Class.isInstanceは、クラス・インターフェイスにインスタンスが自分自身かを確認します。

4-2.Class.isAssignableFromでクラス同士のキャスト可否を判断

isInstanceと似たようなものでClass.isAssignableFromがあります。

このメソッドは、インスタンスが何のクラスか調べるものではなく、クラス同士でinstanceof的な比較をします。

public boolean isAssignableFrom(Class<?> cls)
パラメータ:
	cls - チェック対象のClassオブジェクト
戻り値:
	cls型のオブジェクトがこのクラスに割り当てられるかどうかを示すboolean値
// 基本的な使い方
Class clazz1 = Number.class;
Class clazz2 = Integer.class;

System.out.println(clazz1.isAssignableFrom(clazz2)); // → true (=IntegerはNumberである)
System.out.println(clazz2.isAssignableFrom(clazz1)); // → false (=NumberはIntegerではない)
// メソッドの引数でインスタンスとクラスを受け取り、動的に判断する
class IsAssignableFromTest {
	static void isAssignableFrom(Class classTo, Class classFrom) {
		System.out.println(classTo.isAssignableFrom(classFrom));
	}

	public static void main(String[] args) {
		isAssignableFrom(Integer.class, Integer.class); // → true
		isAssignableFrom(Number.class, Integer.class); // → true
		isAssignableFrom(Long.class, Integer.class); // → false
		isAssignableFrom(Comparable.class, Integer.class); // → true
		isAssignableFrom(AutoCloseable.class, Integer.class); // → false
		isAssignableFrom(String.class, Integer.class); // → false
	}
}

ここで、公式Javadocの「割り当てられるかどうか」という表現は、少し意味が取りづらいです。isAssignableFromの引数になったクラスが、メソッドを実行したクラスにキャストできるかだと考えると、少しは分かりやすいでしょうか。

isAssignableFromは、instanceofとは使い道が厳密には違いますので注意しましょう。でも、インスタンスがない状態で、クラス間での確認ができるのは便利なところですね。


5.まとめ

この記事では、インスタンスの実体が何かを調べる演算子、instanceofの使い方をお伝えしてきました。

なぜこういう演算子があるか。それはJavaが静的型付けが特徴のオブジェクト指向プログラミング言語だからです。

クラスの継承やインターフェイスの実装によって、変数の型は抽象的なクラス・インターフェイスになりえます。その変数が指す先の実体が何か、プログラマは調べる必要があるからです。

今のJavaで普通のプログラムを作っているなら、instanceofはあまり使わないかもしれません。ですが、instanceofはオブジェクト指向プログラミング言語には欠かせないものだとは覚えておきましょう。

私たちは、全てのエンジニアに市場価値を高め自身の望む理想のキャリアを歩んでいただきたいと考えています。もし、今あなたが転職を検討しているのであればこちらの記事をご一読ください。理想のキャリアを実現するためのヒントが見つかるはずです。

『技術力』と『人間力』を高め市場価値の高いエンジニアを目指しませんか?

私たちは「技術力」だけでなく「人間力」の向上をもって遙かに高い水準の成果を出し、関わる全ての人々に感動を与え続ける集団でありたいと考えています。

高い水準で仕事を進めていただくためにも、弊社では次のような環境を用意しています。

  • 定年までIT業界で働くためのスキル(技術力、人間力)が身につく支援
  • 「給与が上がらない」を解消する6ヶ月に1度の明確な人事評価制度
  • 平均残業時間17時間!毎週の稼動確認を徹底しているから実現できる働きやすい環境

現在、株式会社ボールドでは「キャリア採用」のエントリーを受付中です。

まずは以下のボタンより弊社の紹介をご覧いただき、あなたの望むキャリアビジョンをエントリーフォームより詳しくお聞かせください。

コメント

公式アカウントLINE限定!ボールドの内定確率が分かる無料診断実施中
公式アカウントLINE限定!
ボールドの内定確率が分かる無料診断実施中