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.32:ジェネリクスと可変長引数を注意して組み合わせる

Posted at

5.32:ジェネリクスと可変長引数を注意して組み合わせる

要点

ジェネリクス + varargs は heap pollution を引き起こしやすい。@SafeVarargs を付けられる条件を満たし、かつ「varargs 配列を外部に露出しない」実装にするか、そもそも varargs を使わない設計にするのが正解。

なぜ問題になるか

  • T... elems は実行時には T[] elems(配列)となる。
  • ジェネリクスは実行時に型情報が消える(型消去)ため、T[] が実行時にはただの Object[] 的に扱われうる。
  • その配列が他の参照経路から不適切な型で書き換えられると、コンパイル時の型期待とヒープ上の実体が食い違い、取り出したときに ClassCastException を招く(=heap pollution)。

悪い例

1. ジェネリック varargs をそのまま返す / 配列を露出させる

// NG: elems 配列を backing にする Arrays.asList を返している → 配列が外に出る
public static <T> List<T> bad1(T... elems) {
    return Arrays.asList(elems);
}

2. パラメタライズド型の可変長引数

// NG: List<String>... はコンパイラが unchecked 警告を出す典型例
public static void dangerous(List<String>... stringLists) {
    Object[] array = stringLists;            // varargs配列は実際にObject[]
    array[0] = Arrays.asList(42);            // List<Integer> を突っ込めてしまう
    String s = stringLists[0].get(0);        // ClassCastException が発生
}

➡ これが典型的な heap pollution。stringLists の要素が List< String > のはずが List< Integer > になってしまう。

良い例

1. 可能なら varargs を使わない

  • 代わりに Collection< ? extends T > や List< ? extends T> を受ける。
public <T> void addAll(Collection<? extends T> c) { ... } // 柔軟で安全

 
2. どうしても varargs を提供するなら 配列を外に出さない実装 & @SafeVarargs

  • メソッドを static または final にし、かつ配列をそのまま返したり、配列をバックにしたビューを返さない。
@SafeVarargs
public static final <T> List<T> safeListOf(T... elems) {
    // elems 配列の要素をコピーして新しいコレクションを返す
    List<T> list = new ArrayList<>(elems.length);
    Collections.addAll(list, elems);
    return list; // 配列は外に出ない -> 安全
}
  • @SafeVarargs は「このメソッドは型安全に varargs を扱っている」と宣言するもの。付ける前に実装が本当に安全か自分で保証する必要があります。

 
3. パラメタライズド型の varargs(例:List< String >...)は避ける

  • List< String >... のような宣言はコンパイル時に unchecked 警告が出やすく、非常に危険。代替は List< ? extends T > の集合を受け取る API にする。

 
4. どうしても配列を使う必要がある場合(最小化して使う)

  • @SuppressWarnings("unchecked") を 局所化して使う(コメントでなぜ安全かを明記)。
@SuppressWarnings("unchecked")
public static <T> void legacyVariant(Object o) {
    List<T>[] arr = (List<T>[]) new List<?>[10]; // unsafe but documented and local
    // ... 小さく限定して使う
}

→ ただし可能な限り避ける。

まとめ

  • 基本方針:ジェネリックな varargs は便利だが危険。公開 API では避けるか、@SafeVarargs を使える条件を満たして「配列を露出しない」実装に限定する。
  • 代替:Collection< ? extends T >/List< ? extends T > を受ける API にして、必要なら利便性のために安全な varargs オーバーロードを薄く置く。
  • 警告:配列をバックにするビュー(Arrays.asList(elems))をそのまま返す実装は避ける。
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?