Code Fix

上級

raw type とワイルドカードの使い分け

型引数を省くと型安全が失われます。List<Object>とList<?>の違いも整理します。

エラーメッセージの読み方

Main.java:8: error: incompatible types: Object cannot be converted to String
Main.java
ファイル名
8
行番号 — javacが異常に気づいた位置。原因の行とは限りません
error
種別 — コンパイルエラー。実行まで到達しません
incompatible types: Object cannot be converted to String
内容 — javacが期待していたもの、または見つけられなかったもの

このエラーが出る典型パターン

パターン1

 1  import java.util.ArrayList;
 2  import java.util.List;
 3  
 4  public class Main {
 5      public static void main(String[] args) {
 6          List         list = new ArrayList<>();
                    ^
 7          list.add("a");
 8          String s = list.get(0);
 9          System.out.println(s);
10      }
11  }
Main.java:8: error: incompatible types: Object cannot be converted to String

型引数を省くと生の型になり、要素はObjectとして扱われます。取り出すたびにキャストが必要になります。

直し方: (空) を <String> にします。

この問題を解いてみる →

広告
広告スロット(未設定)

パターン2

 1  import java.util.List;
 2  
 3  public class Main {
 4      static void show(List<Object> list) {
                                 ^
 5          for (Object o : list) System.out.println(o);
 6      }
 7      public static void main(String[] args) {
 8          List<Integer> nums = List.of(1, 2, 3);
 9          show(nums);
10      }
11  }
Main.java:9: error: incompatible types: List<Integer> cannot be converted to List<Object>

List<Integer>はList<Object>の部分型ではありません。何でも受けるにはワイルドカード ? を使います。

直し方: Object を ? にします。

この問題を解いてみる →

パターン3

 1  public class Main {
 2      static <T> T first(java.util.ArrayList <T> list) {
                                     ^
 3          return list.get(0);
 4      }
 5      public static void main(String[] args) {
 6          System.out.println(first(java.util.List.of("a")));
 7      }
 8  }
Main.java:6: error: method first in class Main cannot be applied to given types; required: ArrayList<T> found: List<String> reason: cannot infer type-variable(s) T

引数は最も広い型で受けるのが原則です。ArrayListで受けるとListしか持たない呼び出し側が渡せません。

直し方: java.util.ArrayList を java.util.List にします。

この問題を解いてみる →

パターン4

 1  import java.util.ArrayList;
 2  import java.util.List;
 3  public class Main {
 4      public static void main(String[] args) {
 5          List list = new ArrayList();
 6          list.add("a");
 7          list.add(1   );
                       ^
 8          for (Object o : list) {
 9              String s = (String) o;
10              System.out.println(s);
11          }
12      }
13  }
Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.Stringexited with code 1

raw型のListは要素の型をコンパイル時にチェックしません。型の違う値が紛れ込んでも、実際にキャストするまで気づけません。

直し方: 1 を "b" にします。

この問題を解いてみる →

パターン5

 1  import java.util.ArrayList;
 2  import java.util.List;
 3  public class Main {
 4      static List getRaw() {
 5          List list = new ArrayList();
 6          list.add(42  );
                       ^
 7          return list;
 8      }
 9      public static void main(String[] args) {
10          List<String> list = getRaw();
11          String s = list.get(0);
12          System.out.println(s);
13      }
14  }
Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.Stringexited with code 1

raw型を返すメソッドは呼び出し側の型チェックをすり抜けます。警告だけでコンパイルが通り、実際に取り出すまで型の不一致が発覚しません。

直し方: 42 を "ok" にします。

この問題を解いてみる →

よくある誤解

警告だけでコンパイルは通ります。通ってしまうからこそ、本番で ClassCastException として現れます。

実務での勘所

raw typeが今も使える(禁止されていない)のは、Java 5でジェネリクスを導入したときに、それ以前に書かれた大量のコードを壊さないための後方互換性を優先したためです。ジェネリクスの型情報はコンパイル時にしか存在せず、コンパイル後のバイトコードでは型引数の情報が消える(型消去、type erasure)という実装方式が採られました。この方式のおかげでジェネリクス登場前と登場後のコードが同じJVM上で混在して動きますが、その代償としてraw typeを使えばコンパイラの型検査をすり抜けられてしまいます。

演習をはじめる

関連するエラー

広告
広告スロット(未設定)