こんにちは、かつコーチです。
「コンパイルは通ったのに、実行するといきなりClassCastExceptionで落ちた」という経験はありませんか。
今回は、キャスト(型変換)にまつわる実行時エラーの原因と、安全に防ぐ方法を整理します。
ClassCastExceptionとは?互換性のない型への強制変換
なぜコンパイルは通るのか
ClassCastException(略称CCE)は、互換性のない型同士を無理やりキャストしようとしたときに発生する実行時エラーです。
Object obj = "かつコーチ";
Integer num = (Integer) obj; // 実行時にCCE発生
Exception in thread "main" java.lang.ClassCastException:
class java.lang.String cannot be cast to class java.lang.Integer
(java.lang.String and java.lang.Integer are in module java.base
of loader 'bootstrap')
at Main.main(Main.java:2)
コンパイラはObject型の変数がどんな実体を持つかまでは追跡できないため、キャストの構文自体は許可してしまいます。
実際に実体の型が一致しない場合にのみ、実行時にこのエラーが発生する点がコンパイルエラーとの大きな違いです。
エラーメッセージの読み方
メッセージは「class A cannot be cast to class B」という形式で、Aが実際の型、Bがキャスト先の型を示しています。
上記の例では「実体はStringなのにIntegerにキャストしようとした」ことが一目で分かります。
ClassCastExceptionが起きやすい2つの場面
コレクションを混在させて扱う場合
ジェネリクスを使わない、またはObject型のコレクションに複数の型を混在させると発生しやすくなります。
// ❌Before:型を意識せずObjectのリストに混在させる
List<Object> list = new ArrayList<>();
list.add("文字列");
list.add(100);
for (Object item : list) {
String s = (String) item; // 2番目の要素でCCE発生
System.out.println(s.toUpperCase());
}
Exception in thread "main" java.lang.ClassCastException:
class java.lang.Integer cannot be cast to class java.lang.String
at Main.main(Main.java:6)
// ✅After:ジェネリクスで型を統一する
List<String> list = new ArrayList<>();
list.add("文字列");
list.add("もう一つの文字列");
for (String item : list) {
System.out.println(item.toUpperCase()); // キャスト不要で安全
}
ジェネリクス(コレクションなどが扱う型をコンパイル時に指定する仕組み)を正しく使えば、そもそも不正な型が混入すること自体をコンパイル時に防げます。
instanceofを使わずにキャストする場合
継承関係のあるクラス階層で、サブクラス固有の処理をしたい場合にキャストが必要になりますが、確認なしで行うと危険です。
// ❌Before:instanceofで確認せずにキャスト
public void process(Animal animal) {
Dog dog = (Dog) animal; // animalがCatだったらCCE
dog.bark();
}
Exception in thread "main" java.lang.ClassCastException:
class Cat cannot be cast to class Dog
at Main.process(Main.java:2)
// ✅After:パターンマッチング(Java 16以降)で安全に判定
public void process(Animal animal) {
if (animal instanceof Dog dog) {
dog.bark();
} else {
System.out.println("Dog以外の処理");
}
}
Java 21ではinstanceofのパターンマッチングが標準機能として使えるため、instanceofで型を確認しつつ、確認と同時に変数へキャスト結果を束縛できます。(Dog) animalと個別に書くよりも簡潔で、確認漏れによる事故も防げます。
つまずいたポイント:JSONライブラリでの型変換ミス
実務でJSONをMap<String, Object>として読み込み、特定のキーの値を数値として扱おうとしたときにCCEが発生しました。
Exception in thread "main" java.lang.ClassCastException:
class java.lang.String cannot be cast to class java.lang.Integer
at com.example.OrderService.calculateTotal(OrderService.java:15)
// ❌Before:JSONの値の実際の型を確認せずキャスト
Map<String, Object> json = parseJson(response);
Integer quantity = (Integer) json.get("quantity"); // 実際はString "3" だった
原因は、外部APIのレスポンスによってquantityが数値型のときと文字列型のときが混在していたことでした。
// ✅After:文字列変換を経由して安全に数値化する
Map<String, Object> json = parseJson(response);
Integer quantity = Integer.parseInt(String.valueOf(json.get("quantity")));
外部システムから受け取るデータは、こちらの想定通りの型で来るとは限らないため、instanceofでの事前確認や、文字列経由での安全な変換を検討する価値があります。
まとめ
この記事のポイント
- ClassCastExceptionはコンパイルは通っても実行時に互換性のない型変換で発生する
- エラーメッセージの「実体の型」と「キャスト先の型」を読めば原因が特定できる
- コレクションはジェネリクスで型を統一し、混在によるCCEを未然に防ぐ
- キャスト前に
instanceofパターンマッチングで型を確認する習慣をつける
次に読むべき記事
- ジェネリクスの基本的な使い方は「ジェネリクスの基本」の記事へ
- NullPointerExceptionとの違いは「NullPointerExceptionの防ぎ方」の記事へ
タグ: Java, 中級者向け, エラー解決
