JavaマスターJavaプログラムサンプル集例外のサンプル → ClassCastException対策

ClassCastException対策

クラスのキャストに失敗すると発生するClassCastExceptionの実例です。

まずはもっとも基本的なパターンです。 Personクラスと、それを継承したStudentクラスを、相互にキャストする実験です。

samples/exception/cls/ClassCastExceptionTest.java - Eclipse SDK
package samples.exception.cls;

class Person {
  
}
class Student extends Person {
  
}
public class ClassCastExceptionTest {
  public static void main(String[] args) {
    try {
      Person p1 = new Student();
      Student s1 = (Student)p1;
  
      Person p2 = new Person();
      Student s2 = (Student)p2;
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}

コマンド プロンプト

C:\JavaMaster\bin>java -cp . samples.exception.cls.ClassCastExceptionTest 
java.lang.ClassCastException: samples.exception.cls.Person
    at samples.exception.cls.ClassCastExceptionTest.main(ClassCastExceptionTest.java:16)

Person クラスの変数p1 には、実際にはStudentのインスタンスが入ります。 スーパークラスの変数にサブクラスのオブジェクトを代入してもまったく問題ありません。

その後改めてStudentクラスの変数s1へデータを代入しなおしています。 このときは、スーパークラスとして扱われているオブジェクトをサブクラスに 代入するので、キャストが必要になります。

続いて、Person クラスの変数p2 に、Personクラスのオブジェクトを作成して代入しています。 次に、Studentクラスの変数s2に、p2をむりやり代入しようとしています。 サブクラスの変数にスーパークラスのオブジェクトを代入することはできませんが、 キャストを付けることでコンパイルは成功してしまいます。 しかし、実行すると、ClassCastExceptionが発生します。

s1 への代入文と s2 への代入文は、プログラム表記上はまったく 同じに見えますが、きちんと動作するかどうかは、その内容(Person、Student)に よって変わるのです。

次の例です。

samples/exception/cls/ClassCastExceptionTest2.java - Eclipse SDK
package samples.exception.cls;

import java.util.Date;
import java.util.HashMap;

public class ClassCastExceptionTest2 {
  public static void main(String[] args) {
    try {
      HashMap map = new HashMap();
      map.put("xmas""2005/12/25");
      
      // ...
      
      Date date = (Date)map.get("xmas");
      System.out.println(date);
      
    catch (Exception e) {
      e.printStackTrace();
    }
  }
}

コマンド プロンプト

C:\JavaMaster\bin>java -cp . samples.exception.cls.ClassCastExceptionTest2 
java.lang.ClassCastException: java.lang.String
    at samples.exception.cls.ClassCastExceptionTest2.main(ClassCastExceptionTest2.java:14)

この例では、マップに格納する前と後で、データの型をうっかり間違えてしまっているために、 取り出したデータをDate型の変数に入れるためにキャストをしたところ、エラーとなっています。

マップに限らず、キャストを使ったプログラミングでは、 コンパイル時ではなく、実行したときにエラーが起こりがちであり、 注意が必要です。

ClassCastException例外をできる限り防ぐための対策

キャストを使わず、より上位の変数に入れてしまう
たとえば、あるクラスXと、そのサブクラス数種類のデータの組み合わせを 処理するとき、Xのメソッドしか呼び出さないような場合は、 各サブクラスへわざわざキャストせず、Xクラスの変数へ 入れてしまうという方法があります。

これのさらに汎用的なパターンとしては、たとえば、データに対してtoStringだけを実行したい ような場合は、キャストをするかわりに、どんなクラスのオブジェクトでも受け取れる Objectの変数に入れてしまうという、もっとも単純でエラーに強い方法があります。

キャストを実行する前に、instanceof で調べる
調べた結果によって処理を分岐させてやります。 たとえば、マップやリストに数種類のクラスのオブジェクトが入っていて、 それらを1個ずつ処理する場合などは、if文で場合分けを行うということになるでしょう。

ただ、場合分けをした場合、予想外のクラスが来た場合には、どうしようもありません。 結局その場合は何らかのエラー対応処理をするか、例外を投げるか、無視するかという ことになるでしょう。

Java 5以降で使える、クラステンプレートを使う。
これにより、マップやリストの処理で取り出したデータをいちいち キャストする必要がなくなり、また、意図しないクラスがマップやリストに 紛れ込む心配もなくなりますので、より堅牢なプログラムを記述することができるようになります。