Javaのinstanceofとは?インスタンスの実体を判断するinstanceofの構文や使い方
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と同じ深さの継承の階層に、LongやDoubleなどの他の数値を表すクラスがいくつかありますよね。
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の使い道の一つは、変数の型に継承関係がある時に、実際はどのサブクラスなのか知ることです。
ここで「IntegerはNumberである」と言えます。Integerには先に述べたクラスの継承関係があるからです。これは英語だと「Integer is a Number」と書けて、こういう関係を「IS-A関係」とも呼びます。
逆に「NumberはIntegerである(Number is a Integer)」とは100%言い切れません。なぜかというと、Numberが指す変数の先にあるのは、LongやDoubleかもしれないからです。
これらを頭に入れてもらった上で、以下のプログラムを読んでみてください。
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になるのも当たり前
これはコンパイルエラーにはなりませんが、うれしいことも大してありません。
クラスの継承関係上で、IntegerはNumberやObjectだと、最初から分かり切っているからです。
2-3.インターフェイスの実装クラスを知る
instanceofは、型がインターフェイスの場合、その実装クラスが何かを調べる時にも使います。
Comparableは、Javaの中でも一番といっていいくらい広く実装されているインターフェイスです。IntegerはComparableを実装しているので、「IntegerはComparableである」と言えますよね。皆さんがよく使うStringもComparableを実装しているので、「StringはComparableである」とも言えます。
ですから、NumberもStringも、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やSetはObjectを格納し、MapはObjectをキーとし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ですっきり書けないかな…と誰しも考えます。
ですが、残念ながらswitchはClassを値に使えないので、以下のプログラムはコンパイルエラーです。
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のところでコンパイルエラーになってしまうなら、クラスやインターフェイスの継承関係をチェックしてみましょう。
例えば、IntegerとStringはクラスの継承関係上で共通しているのは、Objectであることだけです。ですので、以下のIntegerがStringか確認しているプログラムは、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はオブジェクト指向プログラミング言語には欠かせないものだとは覚えておきましょう。
コメント