先の節で作成した M301プロジェクトおよび、この節で作成する M401, M402プロジェクトにおけるクラスの相関関係を記述しておきます。このあとの節においてさらに構成が複雑になりますので、今のうちからまとめます。
イメージとしては以下のような感じです。このイメージの中に本来はメソッドも記述すべきですが、省略しています。
本節では、この相関関係を前提にして解説をしていきます。
キャストは大きく分けて 4種類に分けられます。具体的には以下の通りです。
ここでは「暗黙的な基本型のキャスト」以外の 3種類について、順番に解説していきます。
なお、キャストは型変換とも呼ばれます。
基本型のキャストについて復習します。以下は、浮動小数点数である 6.5を、整数型の変数 bに代入するソースコードです。
double a = 6.5; int b; b = (int)a;
浮動小数点数の 6.5が int型にキャストされ、変数 bに代入されます。
イメージとしては以下のような感じです。
暗黙的なキャストについては、先の節に登場しました。再掲したいと思います。
Dog dog1 = new WiseDog("ラッキー"); WiseDog wiseDog2 = new WiseDog("ハチ", 10); Dog dog2 = wiseDog2; Object object2 = wiseDog2;
ここにはキャストが省略されており、省略をせずに表現すれば以下のようになるのでした。
Dog dog1 = (Dog)new WiseDog("ラッキー"); WiseDog wiseDog2 = new WiseDog("ハチ", 10); Dog dog2 = (Dog)wiseDog2; Object object2 = (Object)wiseDog2;
このように参照型のキャストが 3ヶ所隠れていました。インスタンスの型よりもスーパークラス側の参照型変数で参照する場合には、キャストを明示する必要はなく、暗黙的にキャストが行われます。
後述する「明示的な参照型のキャスト」では、実行時に例外が発生する場合があります。一方、「暗黙的なキャスト」の場合には、後述するようなキャストに絡む実行時の例外は発生し得ないという特徴があります。そのため、一般的には「暗黙的なキャスト」では記述を省略します。というのも、「暗黙的なキャスト」の場合に、上記のソースコードのように明示して記述してしまうと、「明示的なキャスト」と区別がつかず、誤解し、不安を煽ってしまうためです。本ウェブサイトにおいても、この「暗黙的なキャスト」については、一貫して省略することとします。
M301プロジェクトの Dogクラスと WiseDogクラスをそのまま利用して、明示的なキャストを必要とする例を挙げます。
ソースコードは以下の通り。
M401/Dog.java
/** * 犬を表すクラスです。 */ public class Dog { /** * 名前。 */ protected String name; /** * 鳴く回数。 */ protected int cryCount; /** * コンストラクタ。 * @param name 名前 */ public Dog(String name) { this(name, 1); } /** * コンストラクタ。 * @param name 名前 * @param cryCount 鳴く回数 */ public Dog(String name, int cryCount) { this.setName(name); this.setCryCount(cryCount); } /** * 名前を設定します。 * @param name 名前 */ public void setName(String name) { this.name = name; } /** * 鳴く回数を設定します。 * @param cryCount 鳴く回数 */ public void setCryCount(int cryCount) { this.cryCount = cryCount; } /** * 鳴きます。 */ public void cry() { System.out.print(this.name + "「"); for(int i = 0; i < this.cryCount; i ++) { System.out.print("ワン"); } System.out.println("」"); } /** * お座りします。 */ public void sitDown() { System.out.println(this.name + "は座りました。"); } @Override public String toString() { return "[Dog] name:" + this.name + ", cryCount:" + this.cryCount; } }
M401/WiseDog.java
/** * 賢い犬を表すクラスです。 */ public class WiseDog extends Dog { /** * コンストラクタ。 * @param name 名前 */ public WiseDog(String name) { super(name); } /** * コンストラクタ。 * @param name 名前 * @param cryCount 鳴く回数 */ public WiseDog(String name, int cryCount) { super(name, cryCount); } @Override public void cry() { System.out.print(this.name + "「"); for(int i = 0; i < this.cryCount; i ++) { System.out.print("キャン"); } System.out.println("」"); } /** * お手をします。 */ public void touch() { System.out.println(this.name + "はお手をしました。"); } @Override public String toString() { return "[WiseDog] name:" + this.name + ", cryCount:" + this.cryCount; } }
M401/M401.java
/** * 明示的なキャストを確認します。 */ public class M401 { /** * メインメソッド。 * @param args 引数 */ public static void main(String[] args) { // インスタンスを作成 Object object = new WiseDog("ハチ", 10); Dog dog = (Dog)object; // 「Dog dog = object;」ではビルドエラー WiseDog wiseDog = (WiseDog)object; // 「WiseDog wiseDog = object;」ではビルドエラー // 「ハチ」のインスタンスメソッドを実行 System.out.println("---"); System.out.println("内容は " + wiseDog); wiseDog.cry(); wiseDog.touch(); System.out.println("---"); System.out.println("内容は " + dog); dog.cry(); // dog.touch(); // ビルドエラー System.out.println("---"); System.out.println("内容は " + object); // object.cry(); // ビルドエラー // object.touch(); // ビルドエラー } }
実行結果は以下の通り。
--- 内容は [WiseDog] name:ハチ, cryCount:10 ハチ「キャンキャンキャンキャンキャンキャンキャンキャンキャンキャン」 ハチはお手をしました。 --- 内容は [WiseDog] name:ハチ, cryCount:10 ハチ「キャンキャンキャンキャンキャンキャンキャンキャンキャンキャン」 --- 内容は [WiseDog] name:ハチ, cryCount:10
13行目にて、WiseDog型インスタンスを作成していますが、紐づけている参照型変数 objectは Object型です。14行目、16行目でそれぞれ変数 dog、wiseDog に参照を設定しようとしていますが、ここでは式の右辺が Object型の参照型変数である objectです。このソースコードの構成においては、参照型変数 objectの参照先は「明らかに WiseDog型インスタンス」なのですが、それを Javaに示さないとビルドエラーになってしまいます。そのビルドエラーを回避するために「明示的なキャスト」を記述する必要があります。
このように、本来よりもサブクラス側の参照型変数に参照を設定する場合に、明示的なキャストが必要になります。
「明示的なキャスト」は、ビルドエラーを回避するために記述するとのことでした。しかし、明らかに無理のあるキャストはビルドエラーになります。例えば、以下のような例です。
WiseDog wiseDog = new WiseDog("ラッキー"); Point point = (Point)wiseDog; // ビルドエラー
WiseDog型と Point型には何の縁もありません。本質的に異なるものをキャストすることはできません。基本型のキャストは「値を変換する」ものでしたが、参照型のキャストは「あるインスタンスを別の型だとみなす」というところにあります。WiseDog型インスタンスを Point型とみなすことは無理がありますから、ビルドエラーになります。
M401プロジェクトの Dogクラスと WiseDogクラスをそのまま利用して、明示的なキャストの部分で実行時に例外が発生する例を見ていきます。
ソースコードは以下の通り。
M402/Dog.java
/** * 犬を表すクラスです。 */ public class Dog { /** * 名前。 */ protected String name; /** * 鳴く回数。 */ protected int cryCount; /** * コンストラクタ。 * @param name 名前 */ public Dog(String name) { this(name, 1); } /** * コンストラクタ。 * @param name 名前 * @param cryCount 鳴く回数 */ public Dog(String name, int cryCount) { this.setName(name); this.setCryCount(cryCount); } /** * 名前を設定します。 * @param name 名前 */ public void setName(String name) { this.name = name; } /** * 鳴く回数を設定します。 * @param cryCount 鳴く回数 */ public void setCryCount(int cryCount) { this.cryCount = cryCount; } /** * 鳴きます。 */ public void cry() { System.out.print(this.name + "「"); for(int i = 0; i < this.cryCount; i ++) { System.out.print("ワン"); } System.out.println("」"); } /** * お座りします。 */ public void sitDown() { System.out.println(this.name + "は座りました。"); } @Override public String toString() { return "[Dog] name:" + this.name + ", cryCount:" + this.cryCount; } }
M402/WiseDog.java
/** * 賢い犬を表すクラスです。 */ public class WiseDog extends Dog { /** * コンストラクタ。 * @param name 名前 */ public WiseDog(String name) { super(name); } /** * コンストラクタ。 * @param name 名前 * @param cryCount 鳴く回数 */ public WiseDog(String name, int cryCount) { super(name, cryCount); } @Override public void cry() { System.out.print(this.name + "「"); for(int i = 0; i < this.cryCount; i ++) { System.out.print("キャン"); } System.out.println("」"); } /** * お手をします。 */ public void touch() { System.out.println(this.name + "はお手をしました。"); } @Override public String toString() { return "[WiseDog] name:" + this.name + ", cryCount:" + this.cryCount; } }
M402/M402.java
/** * 明示的なキャストでの実行時例外を確認します。 */ public class M402 { /** * メインメソッド。 * @param args 引数 */ public static void main(String[] args) { // インスタンスを作成 Object object = new Dog("ハチ", 10); Dog dog = (Dog)object; WiseDog wiseDog = (WiseDog)object; // 「ハチ」のインスタンスメソッドを実行 System.out.println("---"); System.out.println("内容は " + wiseDog); wiseDog.cry(); wiseDog.touch(); System.out.println("---"); System.out.println("内容は " + dog); dog.cry(); System.out.println("---"); System.out.println("内容は " + object); } }
実行結果は以下の通り。
Exception in thread "main" java.lang.ClassCastException: Dog cannot be cast to WiseDog at M402.main(M402.java:15)
ビルドエラーが発生しないので、一見問題なさそうに思われますが、プログラムを実行すると強制終了してしまいます。
発生している例外はClassCastExceptionだと分かります。この例外がキャストに関連したものであることは名前から推測できると思いますが、プログラムが強制終了してしまう理由は分かるでしょうか。
今回、強制終了した要素のひとつは、「参照型変数 objectの参照先にあるインスタンスの型は Dog型」ということです。WiseDog型のインスタンスではなく、Dog型のインスタンスを生成しています(13行目)。ところが、15行目において、「そのインスタンスを WiseDog型とみなせ」と記述しています。Dog型を WiseDog型とみなすのには無理があります…なぜなら、もしそれが実現できるとしたら、wiseDog.touch();
と記述できることになります。ところが、Dog型インスタンスには touchメソッドなど存在しませんから、話がおかしなことになってしまいます。
という訳で、Javaでは「ある型のインスタンスを、そのサブクラス側の参照型変数で参照しようとした場合には、実行時例外が発生する」という仕組みになっています。暗黙的な参照型のキャストのところで説明したように、ある型のインスタンスを、そのスーパークラス側の参照型変数で参照するのは大丈夫です。しかし、ある型のインスタンスを、そのサブクラス側の参照型変数で参照することはできません。
オブジェクト指向らしいプログラムを作成すれば、「参照型の明示的なキャスト」が必要になる例は、とても少ないです。したがって、もし、明示的な参照型のキャストが必要になった場合は、プログラムの構成上に何か問題が発生しているのかもしれません。一度、冷静になって構成を考えてみたほうが良いのかもしれません。
考え直してもなお、「参照型の明示的なキャスト」が必要であると言うことならば、キャストの部分で実行時の例外が発生しないように注意を払う必要があります。実行時の例外はプログラムを実行してみなければ分かりませんから、後になってトラブルが発生しないよう念入りにプログラムを作成する必要があります。
なお、本ウェブサイトにおいては、この節のプログラムとこの後の章のごく一部のプログラムを除いて、「参照型の明示的なキャスト」は使用していません。