ダウンキャストは危険
質問:ダウンキャストして、インスタンスの型とキャスト先の型が全然違う時はどうなるの?
解答:ClassCastException例外が投げられます。
前ページで説明したダウンキャスト、実は使う時に注意が必要です。
ダウンキャストした時、インスタンスの型とキャスト先の型が全然違う場合、例外が投げられます。
なぜ例外が投げられるのかと、それを回避する方法を見てみましょう。
/**
* 何も持たないクラス。
* (NoMemberSuperクラスとまったく同じクラスです)
*/
class NoMemberSuper2
{
}
/**
* NoMemberSuper2クラスから継承した、サブクラス。
* メソッドを追加しています。
* (AddedMethodSubクラスとまったく同じクラスです)
*/
class AddedMethodSub2 extends NoMemberSuper2
{
/**
* 自クラスの名前を出力するメソッドです。
*/
void printMyName()
{
System.out.println( "AddedMethodSub2" );
}
}
/**
* 実行用クラス。このクラスを実行してください。
*/
class AddedMethodSub2Runner
{
public static void main( String[] args )
{
// スーパークラスのインスタンスを1つ作ります。
NoMemberSuper2 refSuper = new NoMemberSuper2();
// サブクラスにダウンキャストすると例外が投げられます。
try
{
AddedMethodSub2 refSub = (AddedMethodSub2)refSuper;
refSub.printMyName();
}
catch( ClassCastException e )
{
System.out.println( "ダウンキャストできません!" );
}
// 出力結果:
// ダウンキャストできません!
// インスタンスの型を調べる時はinstanceofを使います。
if( refSuper instanceof NoMemberSuper2 == true )
{
System.out.println( "NoMemberSuper2クラスです。" );
}
// 出力結果:
// NoMemberSuper2クラスです。
if( refSuper instanceof AddedMethodSub2 == false )
{
System.out.println( "AddedMethodSub2クラスじゃない。" );
}
// 出力結果:
// AddedMethodSub2クラスじゃない。
}
}
// AddedMethodSub2Runner.java /** * 何も持たないクラス。 * (NoMemberSuperクラスとまったく同じクラスです) */ class NoMemberSuper2 { } /** * NoMemberSuper2クラスから継承した、サブクラス。 * メソッドを追加しています。 * (AddedMethodSubクラスとまったく同じクラスです) */ class AddedMethodSub2 extends NoMemberSuper2 { /** * 自クラスの名前を出力するメソッドです。 */ void printMyName() { System.out.println( "AddedMethodSub2" ); } } /** * 実行用クラス。このクラスを実行してください。 */ class AddedMethodSub2Runner { public static void main( String[] args ) { // スーパークラスのインスタンスを1つ作ります。 NoMemberSuper2 refSuper = new NoMemberSuper2(); // サブクラスにダウンキャストすると例外が投げられます。 try { AddedMethodSub2 refSub = (AddedMethodSub2)refSuper; refSub.printMyName(); } catch( ClassCastException e ) { System.out.println( "ダウンキャストできません!" ); } // 出力結果: // ダウンキャストできません! // インスタンスの型を調べる時はinstanceofを使います。 if( refSuper instanceof NoMemberSuper2 == true ) { System.out.println( "NoMemberSuper2クラスです。" ); } // 出力結果: // NoMemberSuper2クラスです。 if( refSuper instanceof AddedMethodSub2 == false ) { System.out.println( "AddedMethodSub2クラスじゃない。" ); } // 出力結果: // AddedMethodSub2クラスじゃない。 } }
このプログラムのNoMemberSuper2クラスとAddedMethodSub2クラスは、前ページのNoMemberSuperクラスとAddedMethodSubクラスとまったく同じです。違うのはクラス名だけです。
* 何も持たないクラス。
* (NoMemberSuperクラスとまったく同じクラスです)
*/
class NoMemberSuper2
{
}
/**
* NoMemberSuper2クラスから継承した、サブクラス。
* メソッドを追加しています。
* (AddedMethodSubクラスとまったく同じクラスです)
*/
class AddedMethodSub2 extends NoMemberSuper2
{
/**
* 自クラスの名前を出力するメソッドです。
*/
void printMyName()
{
System.out.println( "AddedMethodSub2" );
}
}
/** * 何も持たないクラス。 * (NoMemberSuperクラスとまったく同じクラスです) */ class NoMemberSuper2 { } /** * NoMemberSuper2クラスから継承した、サブクラス。 * メソッドを追加しています。 * (AddedMethodSubクラスとまったく同じクラスです) */ class AddedMethodSub2 extends NoMemberSuper2 { /** * 自クラスの名前を出力するメソッドです。 */ void printMyName() { System.out.println( "AddedMethodSub2" ); } }
2つのクラスは継承していて、サブクラスでprintMyName()メソッドを追加しています。
さて、まずスーパークラスのインスタンスを作ります。
NoMemberSuper2 refSuper = new NoMemberSuper2();
// スーパークラスのインスタンスを1つ作ります。 NoMemberSuper2 refSuper = new NoMemberSuper2();
スーパークラスのインスタンスを作り、その参照をスーパークラスの参照型変数であるrefSuper変数で受け取りました。
これを、サブクラスであるAddedMethodSub2にダウンキャストすると、どうなるでしょう。
try
{
AddedMethodSub2 refSub = (AddedMethodSub2)refSuper;
refSub.printMyName();
}
catch( ClassCastException e )
{
System.out.println( "ダウンキャストできません!" );
}
// 出力結果:
// ダウンキャストできません!
// サブクラスにダウンキャストすると例外が投げられます。 try { AddedMethodSub2 refSub = (AddedMethodSub2)refSuper; refSub.printMyName(); } catch( ClassCastException e ) { System.out.println( "ダウンキャストできません!" ); } // 出力結果: // ダウンキャストできません!
「AddedMethodSub2 refSub = (AddedMethodSub2)refSuper;」という形でダウンキャストし、printMyName()メソッドを呼び出そうとしています。
ところが、これは失敗して、ClassCastException例外が投げられます。
なぜなら、インスタンスの型はNoMemberSuper2クラスですから、キャスト先のAddedMethodSub2クラスではありません。違うクラスにキャストしようとしたために、例外が投げられたわけです。
なぜこのようなことになるのでしょう。
前ページで説明したように、どのフィールド・メソッドが使えるか、ということは、参照型変数の型で決まります。
このダウンキャストでは、AddedMethodSub2クラスの参照型変数に参照を入れようとしています。
もし入れることができた場合、AddedMethodSub2クラスのprintMyName()メソッドを呼び出すことができます。
でもちょっと待ってください。インスタンスの型はスーパークラスのNoMemberSuper2クラスです。ということは、インスタンスはprintMyName()メソッドを持っていません。
ダウンキャストするということは、キャスト先のフィールド・メソッドが使えるようになるということです。
でもフィールド・メソッド本体をインスタンスが持っていなければ使えるわけがありません。
なので、実際に使う時になってエラーが起きる前に、インスタンスと違う型にダウンキャストすると例外が投げられるようになっているわけです。
インスタンスの型を調べる
質問:インスタンスの型を調べるにはどうすればいいの?
解答:instanceofを使います。
今回の例で、無理なダウンキャストできないことは一目瞭然です。
でも、プログラムによってはそうはいかない場合も多々あります。
たとえば、refSuper変数がメソッドの引数で渡されてきたり、もしくはメソッドの戻り値として返されてきた場合はどうなるでしょう。
refSuper変数はNoMemberSuper2クラスの参照型です。
ということは、refSuper変数が指すインスタンスの型は、NoMemberSuper2クラスのサブクラスである可能性があるわけです。
newしている現場が押さえられればインスタンスがどの型か分かりますが、それが分からなければインスタンスの型も分かりません。
そもそも、ポリモーフィズムはインスタンスの型を考えないので、分からなくて当然ということも言えます。
とはいえ、場合によってはダウンキャストがどうしてもしたい場合もあるでしょう。
そのような場合には、インスタンスの型を調べてダウンキャスト可能かチェックすることになります。
インスタンスの型を調べるにはinstanceofというものを使用します。
if( refSuper instanceof NoMemberSuper2 == true )
{
System.out.println( "NoMemberSuper2クラスです。" );
}
// 出力結果:
// NoMemberSuper2クラスです。
// インスタンスの型を調べる時はinstanceofを使います。 if( refSuper instanceof NoMemberSuper2 == true ) { System.out.println( "NoMemberSuper2クラスです。" ); } // 出力結果: // NoMemberSuper2クラスです。
「参照型変数 instanceof クラス名」という形で、「参照型変数」が指しているインスタンスが「クラス名」かどうかを調べます。
そのクラスだった場合にはtrueを返し、そうじゃない場合にはfalseを返します。
この方法でインスタンスの型を調べ、ダウンキャスト先の型だった場合のみダウンキャストすればいいわけです。
逆に言えば、本当に全然関係ない型をダウンキャストする方法はありません。
今回の例で言えば、NoMemberSuper2クラスのインスタンスをAddedMethodSub2Runnerクラスにダウンキャストすることはできません。
ダウンキャストはあくまで参照型変数を入れ換えるだけです。インスタンスを変化させることはできません。
ダウンキャストが必要な場合には、ダウンキャスト先クラス(サブクラス)のインスタンスをnewで作り、ダウンキャスト元クラス(スーパークラス)のインスタンスから各フィールドの値をコピーするしかないでしょう。もっとも、フィールドがprivateだとそれも無理ですが……。