ClassCastException の原因と直し方
キャストは宣言にすぎず、実体が違えば実行時に落ちます。instanceofによる事前確認を扱います。
エラーメッセージの読み方
Main.java:4: error: incompatible types: String cannot be converted to Integer
Main.java- ファイル名
4- 行番号 — javacが異常に気づいた位置。原因の行とは限りません
error- 種別 — コンパイルエラー。実行まで到達しません
incompatible types: String cannot be converted to Integer- 内容 — javacが期待していたもの、または見つけられなかったもの
このエラーが出る典型パターン
パターン1
1 public class Main { 2 public static void main(String[] args) { 3 Object o = "java"; 4 Integer s = (String) o; ^ 5 System.out.println(s); 6 } 7 }
キャストの結果の型と受け取る変数の型は一致させます。ここはコンパイル時に検出できます。
直し方: Integer を String にします。
パターン2
1 public class Main { 2 public static void main(String[] args) { 3 Object o = "java"; 4 Integer n = (Integer) o; ^ 5 System.out.println(n); 6 } 7 }
キャストは「そう扱う」という宣言にすぎません。実体が違えば実行時に落ちます。instanceofで事前に確認します。
直し方: Integer を Integer にします。
パターン3
1 public class Main { 2 public static void main(String[] args) { 3 Object o = "java"; 4 if (o is String) { ^ 5 System.out.println(((String) o).length()); 6 } 7 } 8 }
型を確認する演算子はinstanceofです。キャストの前に挟むとClassCastExceptionを防げます。
直し方: is を instanceof にします。
パターン4
1 public class Main { 2 public static void main(String[] args) { 3 Object o = Integer.valueOf(5); 4 Double d = (Double) o ; ^ 5 System.out.println(d); 6 } 7 }
IntegerとDoubleはどちらもNumberのサブクラスですが、互いに直接キャストはできません。一度Integerとして受けてからdoubleValue()で変換します。
直し方: (Double) o を ((Integer) o).doubleValue() にします。
パターン5
1 public class Main { 2 static class Animal {} 3 static class Dog extends Animal {} 4 static class Cat extends Animal {} 5 public static void main(String[] args) { 6 Animal a = new Dog(); 7 Animal c = (Cat ) a; ^ 8 System.out.println(c); 9 } 10 }
実体はDogなのにCatへキャストしようとしています。実際のインスタンスと違う兄弟クラスへはキャストできません。
直し方: Cat を Dog にします。
よくある誤解
コンパイルが通ったキャストほど危険です。javacは「あり得るかどうか」しか見ておらず、実際の中身は知りません。
実務での勘所
Java 16以降ではinstanceofにパターンマッチングが導入され、if (obj instanceof String s) { ... }のように書けます。従来のif (obj instanceof String) { String s = (String) obj; ... }と違い、判定と変数への代入が1つの式にまとまるため、判定だけして代入を書き忘れる、あるいは違う変数にキャストするといった手違いが起きなくなります。新しいコードではこちらを使うのが基本になりつつあります。