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