以前にJava Silverの資格の勉強をしていた時に、ダウンキャストに関する問題で苦労した記憶があったので、理解を深める意味も込めて要点を整理してみます。
ダウンキャストとは
ダウンキャストとは、あるインスタンスや変数の型を継承関係にある子クラスの型に変更する処理になります。その名の通り親から子へと下がる(ダウン)型の変更(キャスト)をしているわけですが、型の変更をするだけでインスタンスの実体には変化がない点がポイントになります。
したがって、どのような場合でもこのダウンキャストに成功する訳ではなく、問題ないケース、実行時例外が発生するケース、コンパイルエラーが発生するケースの3パターンがあり、どのパターンに当てはまるかについて試験で狙われることも多いのでそれぞれ整理していきます。
問題ないケース
Object o = new Integer(1);
Integer i = (Integer) o;
1行目では、IntegerクラスのインスタンスをObjectクラス型の変数に代入しています。
*Objectクラスは全クラスに共通する最上位のスーパークラス
ここでは子クラスのインスタンスを親クラスの型に変更しているのでダウンキャストの反対でアップキャストと呼ばれており、アップキャストの場合は親クラスへの型に代入していればキャスト後の型を明示的な指定は不要になります。
(あえて明示的に指定するとしたら以下のように書くことができます。)
Object test = (Object) new Integer(1);
次に2行目では、Object型の変数をInteger型にダウンキャストして、Integer型の変数に代入しています。ダウンキャストの場合はキャスト後の型を明示的に指定する必要があり、(Integer)
の箇所で指定しています。
さて、Java Silverの試験では主に冒頭でお話しした3つのパターンのどれに当てはまるかが問われますが、以下の2つの観点から調べることで求めることができます。
①変更前の型のクラス > 変更後の型のクラス である
②変更前のインスタンスの元クラス = 変更後の型のクラス
または
変更後の型のクラス > 変更前のインスタンスの元クラス
*ここでは親子関係を 親クラス > 子クラス と示しています
1. 2つも当てはまる.. 問題なし
2. ①のみ当てはまる... 実行時例外が発生
3. その他... コンパイルエラーが発生
今回の例では、以下のように2つとも当てはまるため、コンパイルエラーも実行時例外も発生せず問題なしとなります。
①変更前の型のクラス(Object) > 変更後の型のクラス(Integer)
1行目では、Object型の変数に代入しており、2行目でInteger型にダウンキャストしています。
... IntegerクラスはObjectクラスを継承しており親子関係である
... 当てはまる
②変更前のインスタンスの元クラス(Integer) = 変更後の型のクラス(Integer)
1行目では、Integerクラスのインスタンスをアップキャストして変数に代入し、2行目でInteger型にダウンキャストしています。
... Integerクラス = Integerクラスで一致している
... 当てはまる
実行時例外が発生するケース
実行時例外が発生するケースは①は当てはまるものの②に当てはまらないケースで、例としては以下のようなコードが挙げられます。
Object o = new Integer(1);
String s = (String) o;
変更点としては、2行目のキャスト後の型がString型になっている点になります。
①変更前の型のクラス(Object) > 変更後の型のクラス(String)
1行目では、Object型の変数に代入しており、2行目でString型にダウンキャストしています。
... StringクラスはObjectクラスを継承しており親子関係である
... 当てはまる
②変更前のインスタンスの元クラス(Integer) != 変更後の型のクラス(String)
1行目では、Integerクラスのインスタンスをアップキャストして変数に代入し、2行目でString型にダウンキャストしています。
ダウンキャストは型を変えるだけでインスタンス自体は変わらず、Integerクラスのインスタンスの型Integerの親クラスではないString型にすることはできず、実行時にClassCastException
がスローされます。
... IntegerクラスとStringクラスは一致せず、StringクラスはIntegerクラスの親クラスでもない
... 当てはまらない
コンパイルエラーが発生するケース
最後に、その他の場合はコンパイルエラーが発生するケースとなり、例としては以下のようなコードが挙げられます。
Integer i = new Integer(1);
String s = (String) i;
変更点としては、1行目でアップキャストせずIntger型のインスタンスをそのままInteger型の変数に代入している点になります。
①変更前の型(Integer) != 変更後の型(String)
1行目では、IntegerクラスのインスタンスをInteger型の変数に代入し、2行目でString型にダウンキャストしています。
... IntegerクラスはとStringクラスは親子関係ではない
... 当てはまらない
実際には①に当てはまらない時点でコンパイルエラーとなりますが、
以下のように2にも当てはまらないことを確認できます。
②変更前のインスタンス(Integer) != 変更後の型(String)
... IntegerクラスとStringクラスは一致せず親子関係ではない
... 当てはまらない
まとめ
・ダウンキャストはあるインスタンスや変数の型を継承関係にある子クラスの型に変更する処理
・問題ないケース、実行事例が発生するケース、コンパイルケースの3パターンに分けられる
・3パターンのうちどれに当てはまるかは以下の2つの観点から確認して求めることができる
①変更前の型のクラス > 変更後の型のクラス である
②変更前のインスタンスの元クラス = 変更後の型のクラス
または
変更後の型のクラス > 変更前のインスタンスの元クラス
*ここでは親子関係を 親クラス > 子クラス と示しています
1. 2つも当てはまる.. 問題なし
2. ①のみ当てはまる... 実行時例外が発生
3. その他... コンパイルエラーが発生