生まれるべくして生まれてきた演算子、instanceof(Java)~instanceof演算子の使い方~


注意書き

使用されるクラスの詳細について

この記事は「ダウンキャストについてわかりやすく書いたった」の続きで書いている。なので、一部のソースコード(Catクラス、Dogクラス、Life_formクラス)などは一番下の「説明に使用したクラスなどの詳細」項目に掲載しておく。

本文は「instanceof演算子とは」からスタートしている。

アップキャストとダウンキャストについて

本記事ではアップキャストとダウンキャストについて軽くおさらいしているが、もっと
詳しく知りたい人は以下の記事へどうぞ。
- 「仕様変更時に変更量を減らせるアップキャスト(Java)
- 「Javaのダウンキャストについてわかりやすく書いたった

開発環境

使用テキストエディタ Atom(以下atom -vertion実行結果)

Atom : 1.28.0
Electron: 2.0.3
Chrome : 61.0.3163.100
Node : 8.9.3

~以下、コマンド「java -version」実行結果~

java version "1.8.0_161"
Java(TM) SE Runtime Environment (build 1.8.0_161-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.161-b12, mixed mode)

アップキャストとダウンキャスト

instanceofの説明に入る前に軽いおさらい

instanceof演算子も、アップキャスト、ダウンキャストと密接に関係している。
そのため、少しおさらいしよう。

アップキャストとは、親クラスインスタンスを子クラスインスタンスで初期化することだ。
これは、設計の段階で子クラスがどれだけ作られるか決まりきっていないような場合でも、引数を親クラスインスタンスにしておくことで、どんな子クラスインスタンスの受け渡しも可能になる点が便利である。

しかし、これには問題もある。先日投稿した「Javaのダウンキャストについてわかりやすく書いたった」で説明した通り、アップキャストを使用した状態では子クラス独自の関数を呼び出せなくなる

そこでダウンキャストを行うのだが……。
ここからが本題だ。

中身、なんですか?

以下のプログラムを見て、何が起きるかを予想してみてほしい。

なお、クラスの詳細については「説明に使用したクラスなどの詳細」に載せている。

Main.java
public class Main{
  public static void main(String[] args){
    Life_form life_form = new Life_form();

    java.util.Random r = new java.util.Random();
    if( r.nextInt(2) == 0 ){
      life_form = new Cat();

    }else{
      life_form = new Dog();

    }

//ダウンキャストからの独自関数呼び出し。
    Cat lifo = (Cat)life_form;
    lifo.catPunch();

  }
}

実行結果は以下のようになる

実行結果

プログラムの説明をしよう。
ランダムな数字を生成し、0だったならばCatインスタンスを、1だったならばDogインスタンスをアップキャストしている。

そして、Catクラスにダウンキャストして、Catクラス独自の関数catPunch()を呼び出そうとしている。

実行して見ると、catPunch()がうまく呼ばれている場合と、ClassCastExceptionエラーになっている場合と二つある。

生成されたインスタンスがCatのものだった場合のみ、正常に動作しているというのが見て取れるだろう。

つまり、逆に言えば、Dogインスタンスが生成されたらエラーになってしまうということだ。

当然だろう。if文のブロックを見ればわかるように、Life_formインスタンスの実体はDogインスタンスの可能性もあるのだ。
無理やりCatインスタンスにダウンキャストしようとしても、実体がDogインスタンスなのにCatインスタンスになれるわけがない。

子クラス独自の関数を呼びたいなら、親クラスインスタンスの実体が何かわかっていなければならないのだ

instanceof演算子

このように、せっかくアップキャストで子クラスインスタンスを網羅的に保持できても、中身が分からなければ適切なキャストができず、結果的に子クラス独自の関数も呼ぶことができない。
これは不便だ。

インスタンスの実体が調べたい。
それを実現するためにinstanceof演算子があるのである

インスタンス instanceof 型名

この構文でインスタンスの実体がなんなのかを調べることができる。
インスタンスの実体と型名が一致しているならtrueとなる。一致していなければもちろんfalseが戻り値として返ってくる。

これを踏まえて先ほどのプログラムの手直ししたものを下記に示す。

成功例

Main.java
public class Main{
  public static void main(String[] args){
    Life_form life_form = new Life_form();

    java.util.Random r = new java.util.Random();
    if( r.nextInt(2) == 0 ){
      life_form = new Cat();

    }else{
      life_form = new Dog();

    }

//インスタンス instanceof 型名
    if( life_form instanceof Cat ){
      Cat cat = (Cat)life_form;
      cat.catPunch();

    }else{
      Dog dog = (Dog)life_form;
      dog.dogAttack();

    }

  }
}

実行結果

きちんと、実体別の振る舞いが出力されている
if文条件式のinstanceof演算子によって適切なダウンキャストが実現できている点に注目だ。
また、生成されたインスタンス独自の関数がきちんと呼べている点も確認してほしい。

このように、instanceof演算子を使うことで安全なダウンキャストを実現することができるのだ

まとめ

  • instanceof演算子は、 インスタンス instanceof 型名 で使用し、インスタンスの実体と型名があっているならtrue、あっていないならfalseが戻り値になる。
  • instanceof演算子を使えば、インスタンスの実体別に振る舞いを変えることが可能になる。

  • instanceof演算子を使うことで、アップキャストされた子クラスインスタンス独自の関数を使いたい時に、安全なダウンキャストを実現できる。

説明に使用したクラスなどの詳細

Life_form.java
//親クラス 
public class Life_form{
  public void makeSound(){
    System.out.println("???");
  }
}
Cat.java
public class Cat extends Life_form{
  @Override
  public void makeSound(){
    System.out.println("にゃあ");
  }

  public void catPunch(){
    System.out.println("ネコパン");
  }

}
Dog.java
public class Dog extends Life_form {
  @Override
  public void makeSound(){
    System.out.println("クゥーン!");
  }

  public void dogAttack(){
    System.out.println("かみつく");
  }
}