0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Effective Java 5.27:無検査警告を取り除く

0
Posted at

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 などを活用する。
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?