ダウンキャストの可否
質問:キャストしようとしたらコンパイルエラーになりました。
解答:キャストした時に、コンパイルエラーになる場合と、実行時に例外が投げられる場合があります。
クラス関係が複雑になり、さらにダウンキャストしまくると、キャストがコンパイルエラーになったり実行時に例外が投げられたりと、事態が複雑になっていきます。
どういう時にうまくいって、どういう時にうまくいかないのか分けて考えてみましょう。
/**
* 何も持たないクラス。
*/
class RootSuper
{
}
/**
* 何も持たないクラス。
* RootSuperクラスのサブクラス。
*/
class LeftSub extends RootSuper
{
}
/**
* 何も持たないクラス。
* RootSuperクラスのサブクラス。
*/
class RightSub extends RootSuper
{
}
/**
* 実行用クラス。このクラスを実行してください。
*/
class RightSubRunner
{
public static void main( String[] args )
{
// RightSubクラスのインスタンスを1つ作ります。
// 参照の型も同じです。
RightSub refRightSub = new RightSub();
// 直接LeftSubクラスへはキャストできません!
// LeftSub refLeftSub = (LeftSub)refRightSub;
// コンパイルエラー:
// RightSubRunner.java:38: 変換できない型
// 検出値 : RightSub
// 期待値 : LeftSub
// LeftSub refLeftSub = (LeftSub)refRightSub;
// ^
// でも、スーパークラスを通すとコンパイルエラーが起きません。
// ただし、実行時に例外が投げられるので実際にはできません。
try
{
RootSuper refRootSuper = refRightSub;
LeftSub refLeftSub = (LeftSub)refRootSuper;
}
catch( ClassCastException e )
{
System.out.println( "ダウンキャストできません!" );
}
// 出力結果:
// ダウンキャストできません!
}
}
// RightSubRunner.java /** * 何も持たないクラス。 */ class RootSuper { } /** * 何も持たないクラス。 * RootSuperクラスのサブクラス。 */ class LeftSub extends RootSuper { } /** * 何も持たないクラス。 * RootSuperクラスのサブクラス。 */ class RightSub extends RootSuper { } /** * 実行用クラス。このクラスを実行してください。 */ class RightSubRunner { public static void main( String[] args ) { // RightSubクラスのインスタンスを1つ作ります。 // 参照の型も同じです。 RightSub refRightSub = new RightSub(); // 直接LeftSubクラスへはキャストできません! // LeftSub refLeftSub = (LeftSub)refRightSub; // コンパイルエラー: // RightSubRunner.java:38: 変換できない型 // 検出値 : RightSub // 期待値 : LeftSub // LeftSub refLeftSub = (LeftSub)refRightSub; // ^ // でも、スーパークラスを通すとコンパイルエラーが起きません。 // ただし、実行時に例外が投げられるので実際にはできません。 try { RootSuper refRootSuper = refRightSub; LeftSub refLeftSub = (LeftSub)refRootSuper; } catch( ClassCastException e ) { System.out.println( "ダウンキャストできません!" ); } // 出力結果: // ダウンキャストできません! } }
このプログラムには1つのスーパークラスと2つのサブクラスがあります。
* 何も持たないクラス。
*/
class RootSuper
{
}
/**
* 何も持たないクラス。
* RootSuperクラスのサブクラス。
*/
class LeftSub extends RootSuper
{
}
/**
* 何も持たないクラス。
* RootSuperクラスのサブクラス。
*/
class RightSub extends RootSuper
{
}
/** * 何も持たないクラス。 */ class RootSuper { } /** * 何も持たないクラス。 * RootSuperクラスのサブクラス。 */ class LeftSub extends RootSuper { } /** * 何も持たないクラス。 * RootSuperクラスのサブクラス。 */ class RightSub extends RootSuper { }
RootSuperがスーパークラスです。
LeftSubクラスとRightSubクラスが、そのクラスから継承したサブクラスです。
さて、まずRightSubクラスのインスタンスを作ります。
// 参照の型も同じです。
RightSub refRightSub = new RightSub();
// RightSubクラスのインスタンスを1つ作ります。 // 参照の型も同じです。 RightSub refRightSub = new RightSub();
サブクラスの1つ、RightSubクラスのインスタンスを作り、その参照を同じRightSubクラスの参照型変数であるrefRightSub変数で受け取りました。
これを、同じサブクラスであるLeftSubクラスにキャストすると、どうなるでしょう。
LeftSub refLeftSub = (LeftSub)refRightSub;
// コンパイルエラー:
// RightSubRunner.java:38: 変換できない型
// 検出値 : RightSub
// 期待値 : LeftSub
// LeftSub refLeftSub = (LeftSub)refRightSub;
// ^
// 直接LeftSubクラスへはキャストできません! LeftSub refLeftSub = (LeftSub)refRightSub; // コンパイルエラー: // RightSubRunner.java:38: 変換できない型 // 検出値 : RightSub // 期待値 : LeftSub // LeftSub refLeftSub = (LeftSub)refRightSub; // ^
このようにコンパイルエラーになります。
そもそも、キャストが可能なのは、キャスト先がインスタンスの型か、そのスーパークラスの型の場合に限ります。
もう少し具体的に言えば、インスタンスの中にある型にのみキャストできるということです。
サブクラスのインスタンスはスーパークラスのインスタンスを含んでいます。そのため、スーパークラスへはアップキャストできます。また、元の型にダウンキャストで戻すこともできます。
でも、インスタンスの中に含まれていない型へはキャストできません。
それを踏まえてこの例を考えてみましょう。
「LeftSub refLeftSub = (LeftSub)refRightSub;」というキャストを行おうとした時、キャスト元のrefRightSub変数はRightSubクラスです。
ということは、refRightSub変数の参照先は、絶対に「RightSubクラスかそのサブクラスのインスタンス」ということです。参照先のインスタンスがRootSuperクラスだったりLeftSubクラスだったりすることはあり得ません。
ということは、そのインスタンスが、キャスト先である「LeftSubクラス」のインスタンスを含んでいることは絶対にありません。
それが、「refRightSub変数の型がRightSubクラスの参照型」ということから分かるため、コンパイラはこれをコンパイルエラーにするわけです。
ここで重要なのは、「キャストできるかどうか」の判断を「キャスト元の参照型変数の型」でしているという点です。
そのため、たとえば次のようにアップキャストすることで、その判断ができなくさせることも可能です。
LeftSub refLeftSub = (LeftSub)refRootSuper;
RootSuper refRootSuper = refRightSub; LeftSub refLeftSub = (LeftSub)refRootSuper;
一度、スーパークラスであるRootSuperクラスへとアップキャストしています。
これはもちろんアップキャストなので問題ありません。参照先のインスタンスはRootSuperクラスのインスタンスを持っているわけですから。
次が問題です。
スーパークラスにアップキャストしたものを、LeftSubクラスにダウンキャストしています。
つまり、さっきはできなかったキャストが、スーパークラスを経由することでできちゃっているというわけです。
これも、先ほど説明したように、「キャストできるかどうか」の判断を「キャスト元の参照型変数の型」でしているため可能になります。
refRootSuper変数の型はRootSuperクラス、つまりLeftSubクラスとRightSubクラスのスーパークラスです。
ということは、refRootSuper変数が参照しているインスタンスは、LeftSubクラスやRightSubクラスの可能性があるわけです。実際のインスタンスはともかく、refRootSuper変数の型で判断すればそうなります。
なので、そこからLeftSubクラスへとダウンキャストすることが可能、というわけです。
ただし、この方法でキャストしても実行時にはエラーになります。
なぜなら、インスタンスの中にキャスト先の型のインスタンスは含まれていないのですから。
つまり、これはコンパイルエラーにならないというだけのことで、結局このキャストはできないのです。
言い換えると、実行時にできないことをコンパイルエラーという形で教えてくれているということです。
インスタンスの型は、ポリモーフィズムにより状況によって変わります。
でも、変数の型によってはインスタンスの型が絞り込める場合があります。その場合、その型をヒントに、キャストできないキャストをしようとしたらコンパイラがエラーで教えてくれるというわけです。