Javaのダウンキャストについてわかりやすく書いたった


前提条件

この記事は「仕様変更時に変更量を減らせるアップキャスト(Java)」の続きで書いている。なので、一部のソースコード(Catクラス、Dogクラス、Life_formクラス)などは一番下の「ソースコード」項目に掲載しておいた。

本文は「ダウンキャストとは」からスタートする。

開発環境

使用テキストエディタ 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)

ダウンキャストとは

 「仕様変更時に変更量を減らせるアップキャスト(Java)」では、アップキャストについて取り扱ったが、この反対の動作をダウンキャストという。

しかし、そう簡単にまとめてしまってはならない。

アップキャストは安全な型変換だった。
子クラスは親クラスを継承しているため、親クラスのフィールドや関数を全て持っている。だから、親クラスのインスタンスに子クラスを代入した(アップキャストした)としても、問題なく動作するのは道理だ。

だが、ダウンキャストはそういうわけにもいかない。
親クラスは子クラスが独自に持つ関数を持っていない。子クラスの宣言をしているのに、子クラスが持っているはずの関数、フィールドを持っていないというのは、少々おかしな話だろう。

実際、ダウンキャストをやってみるとこんな風にコンパイラに怒られる。

失敗例1

Main.java
public class Main{
  public static void main(String[] args){

    //ダウンキャスト 
    Cat cat = new Life_form();       //サブクラス <ー スーパークラス
    cat.makeSound();
  }
}

実行結果

ダウンキャスト(子クラスCatに対して親クラスLife_formを代入)しようとすると、このように、コンパイラさんは型が不適合だと指摘してくれる。

じゃあこれ(↓)ならどうか。

失敗例2

Main.java
// サブクラス <ー スーパークラス
public class Main{
  public static void main(String[] args){
    Life_form life_form = new Life_form();
    Cat cat = new Cat();

    //ダウンキャスト 
    cat = (Cat)life_form;       //サブクラス <ー スーパークラス
    cat.makeSound();
  }
}

上記プログラムのように無理くりキャストしてやればコンパイルは通る。だが、今度は実行時エラーが発生してしまう。

Exception in thread "main" java.lang.ClassCastException: Life_form cannot be cast to Cat
at Main.main(Main.java:31)

やっぱり型が不適合だと指摘されている。

じゃあ、どうすればいいのか。

実はダウンキャストは、アップキャストと同じように直接使用するものではない。
ダウンキャストでは、スーパークラスのインスタンスの実体がサブクラスでなければならないのだ。
つまり、親クラスをそのまま子クラスに入れることはできない。

次に示すコードのように、スーパークラスをサブクラスでインスタンス化し、それをダウンキャストすれば良いのだ。

成功例

Main.java
public class Main{
  public static void main(String[] args){

    //アップキャスト
    Life_form life_form = new Cat();      //スーパークラス <ー サブクラス

    //ダウンキャスト
    Cat cat = (Cat)life_form;        //サブクラス <ー スーパークラス
    cat.catPunch();
  }
}

実行結果

このように書けばきちんと実行できる。ちゃんと(Cat)でキャストしてやる必要がある点に要注意だ。

どんな時にダウンキャストを使用するのか。

アップキャストしたままじゃ、catPunch()は呼べない

ダウンキャストはアップキャストと密接に関係しているので、少しおさらいしよう。

関数の引数にクラスAのインスタンスを使いたいけど、クラスBのインスタンスも指定したい。
そんなシチュエーションで利用するのがアップキャストだった。

CatをLife_fomeにアップキャストした場合、一応Life_formの実体はCatクラスのインスタンスであり、CatクラスではmakeSound()関数をオーバーライドしているため、
life_form.makeSound();
の実行結果はCatクラスのmakeSound()になる。

だが、Catクラス特有の関数catPunch()は呼ぶことができない。実体はCatでも、暗黙的にLife_formとして振る舞うからだ

アップキャストした状態で、catPunch()を実行してみるとこんな風に指摘される。

life_formがCatでインスタンス化されているのにも関わらず、「シンボルが見つかりません」と指摘されるのはlife_formが暗黙的に親クラスとして振舞っているからだ。

じゃあ、どうやってcatPunch()を呼ぶの?

簡単だ。ダウンキャストしてやればいい。
「成功例」の項目にあるコードとその実行結果を見てもらえば、catPunch()を利用できていることが確認できる。

各インスタンスにしかない関数を使いたい時にダウンキャストは使用されるということを覚えておこう。

まとめ

  • ダウンキャストはアップキャストされたものに対して使っていく。
  • 使用するのは、アップキャストした子クラスインスタンス固有の関数を使用したい時。

ソースコード

本記事で使用されるクラスの詳細。

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("かみつく");
  }
}