5.27:無検査警告を取り除く
要点
unchecked 警告は「コンパイラが型安全を保証できない箇所」の合図。放置せず、まずは根本原因を潰して(ジェネリクス化・境界付け・ヘルパー化などで)警告を消し、どうしても残る場合は局所的に @SuppressWarnings("unchecked") を使って安全性を文書化する。
なぜ重要か
- unchecked 警告は「ここで型安全性が保証されていない → 実行時に ClassCastException が発生する可能性がある」という 有効な警告。
- 警告を無視すると、後でバグ(heap pollution、型キャスト失敗)を見つけにくくなる。
- 警告を取り除くことはコード品質・保守性の向上につながる。
典型的な原因と対処法
1. 原因:生(raw)型の使用
// 悪い
List raw = new ArrayList();
raw.add("x");
List<String> strs = raw; // unchecked 警告
対処: 型パラメータを明示する(原型を使わない)
// 良い
List<String> raw = new ArrayList<>();
2. 原因:unchecked キャスト(例:API から Object を受け取ってジェネリック型へ cast)
// 悪い
Object o = legacyApi();
List<String> list = (List<String>) o; // unchecked cast 警告
対処:
- 可能なら legacyApi をジェネリクス化する(ソースを直せるならそちらがベスト)。
- どうしても変換が必要ならランタイムチェック付きの方法(下記 Collections.checkedXxx)を使うか、局所的な @SuppressWarnings とコメントで安全性を示す。
例:runtime-checked wrapper
List<?> raw = (List<?>) o;
List<String> safe = Collections.checkedList((List) raw, String.class);
Collections.checkedList は実行時に要素の型チェックを行い、誤った型が入ると早めに例外を投げます。
※Collections.checkedList(list, String.class) は 「以後このビュー経由で行われる追加・更新操作を実行時に String かどうか検査する」 ラッパー
3) 原因:ジェネリクスと可変長引数(varargs)による heap pollution
// 悪い:可変長引数をジェネリック型で使うと警告。呼び出し時に配列が生成され、型安全でない可能性あり
public static <T> List<T> makeList(T... elems) { return Arrays.asList(elems); }
ジェネリクス + 可変長引数(型...) は以下のような可能性がある。
// 悪い例:varargs の配列を Object[] 経由で汚染するパターン
public static void dangerous(List<String>... stringLists) {
Object[] array = stringLists; // varargs 配列を Object[] にキャスト
array[0] = Arrays.asList(42); // List<Integer> を突っ込む(heap pollution)
String s = stringLists[0].get(0); // ClassCastException が発生する
}
対処:
- Java 7 以降、@SafeVarargs を使えるのは static または final メソッド・コンストラクタだけ。
- それが使えない場合は List.of(elems) か Arrays.asList でも注意。あるいは List result = new ArrayList<>(); for (T e : elems) result.add(e); のように配列のまま外に出さない。
- 重要:@SafeVarargs を付けるのは「メソッド内で varargs 配列を露出せず、外部に heap-pollution を引き起こさない」ことを確実に満たす場合のみ。(要はheapPollutionが絶対に起きない場合のみ)
良い例(@SafeVarargs の正しい使用例):
@SafeVarargs
public static <T> List<T> listOf(T... elems) {
return Arrays.asList(elems); // elems 配列を外部に渡さず安全なら OK
}
4) 原因:ジェネリック配列の生成
// NG
List<String>[] array = new List<String>[10]; // コンパイルエラー(ジェネリック配列は禁止)
対処:
- 配列を使わず List< List< String > > や List< ? >[] を避けるか @SuppressWarnings("unchecked") を使って List[] array = (List< String >[]) new List[10]; を局所的に許容する(ただし気をつける)。
- 可能なら、以下のように配列を使わない。
List< List< String > > outer = new ArrayList<>(10);
for (...) outer.add(new ArrayList<>());
5) 原因:API 設計の問題(戻り値や引数が具体型で縛られる)
対処:
- 戻り値・引数を原則 パラメータ化型 や ワイルドカード(?) を使って書く。List< ? > や List< T > のほうが柔軟で型安全。
- ジェネリックメソッドで型を推論させる。
@SuppressWarnings("unchecked") の使い方(最小化と文書化)
- 最後の手段としてのみ使う。
- 範囲は最小にする。(メソッド全体ではなく、単一行・単一文に限定する)
- 必ずコメントを付ける:なぜ安全なのかを説明する(将来のメンテナ向け)。
// legacyCallの戻り値は、List<String>であるため安全
@SuppressWarnings("unchecked")
List<String> list = (List<String>) legacyCall();
実用的テクニック(よく使う安全な回避策)
1. キャプチャヘルパー(ワイルドカードのキャプチャ)
ワイルドカードから安全にキャストするパターン:
// ヘルパーで capture を使う
public static <T> T capture(Object o) {
return (T) o; // ここだけ @SuppressWarnings して局所化するパターンもある
}
だが 安易な汎用キャストは危険。使うなら十分な説明を付ける。
2. Array.newInstance を使ってジェネリック配列を安全に作る
@SuppressWarnings("unchecked")
public static <T> T[] newArray(Class<T> type, int size) {
return (T[]) Array.newInstance(type, size);
}
ここでの @SuppressWarnings を局所化して、なぜ安全かをコメント。
3. Collections.checkedXxx でランタイム安全性を補強
List<?> raw = (List<?>) legacy();
List<String> checked = Collections.checkedList((List) raw, String.class);
→ compile-time の警告は出るが、誤った挿入を早期に発見できる。
4. heap pollution(浅い説明)
- ジェネリック型の配列が実行時には原型(raw)配列として扱われることで、異なる型の参照が混入し、予期せず ClassCastException が発生する状況を「heap pollution」という。
- varargs + ジェネリクス + 生配列 は特に危険。@SafeVarargs は使い方を正確に守れば OK。
良い例 / 悪い例まとめ
悪い:
List raw = new ArrayList();
raw.add("x");
List<String> s = raw; // unchecked 警告
良い:
List<String> raw = new ArrayList<>();
悪い(ジェネリック配列):
List<String>[] array = new List<String>[10]; // NG
良い(代替):
List<List<String>> outer = new ArrayList<>();
for (int i=0;i<10;i++) outer.add(new ArrayList<>());
悪い(無検査 suppress を広く使う):
@SuppressWarnings("unchecked")
public void foo() {
// 広い範囲のuncheckedはNG
}
良い(局所 suppress + doc):
// legacyApiの戻り値は、List<String>とドキュメントされているため安全
@SuppressWarnings("unchecked")
List<String> list = (List<String>) legacyApi();
まとめ
- unchecked 警告は「放置して良い」ではなく「修正すべき合図」。
- まず 型パラメータ化・ワイルドカード・ジェネリックメソッド・ヘルパー化 で警告を消す。
- やむを得ない場合に限り、局所的に @SuppressWarnings("unchecked") を使い、必ず理由をコメントで残す。
- varargs/generic の組合せ・ジェネリック配列は特に注意し、@SafeVarargs / Array.newInstance / Collections.checkedXxx などを活用する。