Effective Java 第３版 第５章ジェネリックス


Java中級者以上の必須本である、[Effective Java 第３版](https://www.amazon.co.jp/Effective-Java-%E7%AC%AC3%E7%89%88-%E3%82%B8%E3%83%A7%E3%82%B7%E3%83%A5%E3%82%A2%E3%83%BB%E3%83%96%E3%83%AD%E3%83%83%E3%82%AF-ebook/dp/B07RHX1K53)に Kindle版が出たので、まとめる。

前：[Effective Java 第３版 第４章クラスとインタフェース](https://qiita.com/tseno/items/3ac7e014619dee6e8f57)
次：[Effective Java 第３版 第６章enumとアノテーション](https://qiita.com/tseno/items/05a3cc4bed7c8098d311)

### 用語
- パラメータ化された型・・・`List<String>`
- 実型パラメータ・・・`String`
- ジェネリック型・・・`List<E>`
- 仮型パラメータ・・・`E`
- 非境界ワイルドカード型・・・`List<?>`
- 原形・・・`List`
- 境界型パラメータ・・・<`E extends Number>`
- 再帰型境界・・・`<T extends Comparable<T>>`
- 境界ワイルドカード型・・・`List<? extends Number>`
- ジェネリックメソッド・・・`static <E> List<E> asList(E[] a)`
- 型トークン・・・`String.class`

# 項目２６　原型を使わない

- 型パラメータを伴わないジェネリック型である、原型を使わない。(`List`等で`<>`の無いもの)
- `List<Object>`を使うのは構わない。`List`（原型）には、`List<String>`を渡すことができるが、`List<Object>`には渡せないので安全。
- `Set<Object>`は、任意の型のオブジェクトを含むことが可能なSetを表すパラメータ型（安全）。
- `Set<?>`は、何らかの不明な型のオブジェクトを含むことが可能なSetを表すワイルドカード型（安全）。

```java:原型の例
// NG
private final Collection stamps = ... ;
// OK
private final Collection<Stamp> stamps = ... ;

// NG
static int numElementsInCommon(Set s1, Set s2) { ... }
// OK
static int numElementsInCommon(Set<?> s1, Set<?> s2) { ... }
```

# 項目２７　無検査警告を取り除く

- 全ての無検査警告は、実行時の`ClassCastException`の可能性を表している。
- 無検査警告を取り除くことができない場合、`@SuppressWarnings("unchecked")`アノテーションにより警告を抑制し、理由をコメントに残す。

```java:@SuppressWarnings("unchecked")アノテーションによる警告を抑制
public <T> T[] toArray(T[] a) {
  if (a.length < size) {
    // T[] として渡されたのと同じ型の配列を生成するので、このキャストは正しい
    @SuppressWarnings("unchecked") T[] result = (T[]) Arrays.copyOf(elementData, size, a.getClass());
    return result;    
  }
  System.arraycopy(elements, 0,va, 0, size);
  if (a.length > size)
    a[size] = null;
  return a;
}
```

# 項目２８　配列よりもリストを選ぶ

- 配列は欠陥があり、ジェネリックスとうまく調和しない。`new List<E>[]`、`new List<String>[]`、`new E[]`はどれも許されていない。
- 配列はコンパイル時の型安全を提供しないが、ジェネリックスは型安全である。

# 項目２９　ジェネリック型を使う

```java:ジェネリクス未使用
public class Stack {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        this.elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public Object pop() {
        if (size == 0)
            throw new EmptyStackException();
        Object result = elements[--size];
        elements[size] = null;  // ★使われなくなった参照を取り除く
        return result;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    private void ensureCapacity() {
        if (elements.length == size) {
            elements = Arrays.copyOf(elements, 2 * size + 1);
        }
    }
}
```

- `Object`を`E`に変更する。

```java:ジェネリクスへの書き換え例
public class Stack<E> {
    private E[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    @SuppressWarnings("unchecked")    // 警告を抑制する
    public Stack() {
        // new E[] はできないので、Object[] を E[] にキャストする（無検査警告が出る）
        this.elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(E e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public E pop() {
        if (size == 0)
            throw new EmptyStackException();
        E result = elements[--size];
        elements[size] = null;  // ★使われなくなった参照を取り除く
        return result;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    private void ensureCapacity() {
        if (elements.length == size) {
            elements = Arrays.copyOf(elements, 2 * size + 1);
        }
    }
}
```

# 項目３０　ジェネリックメソッドを使う

- ジェネリックメソッドの、型パラメータを宣言する型パラメータのリスト`<E>`は、メソッド修飾子とメソッドの戻り値型の間に入る。
- 境界ワイルドカード型を使うことでもっと柔軟にできる。

```java:ジェネリックメソッド
    public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
        Set<E> result = new HashSet<>(s1);
        result.addAll(s2);
        return result;
    }
```

# 項目３１　APIの柔軟性向上のために境界ワイルドカードを使う

```java:境界ワイルドカードを使った例
    // Eプロデューサ（生産者）としてのパラメータのためのワイルドカード型
    public void pushAll(Iterable<? extends E> src) {
        for (E e : src) {
            push(e);
        }
    }

    // Eコンシューマ（消費者）としてのパラメータのためのワイルドカード型
    public void popAll(Collection<? super E> dat) {
        while (!isEmpty()) {
            dat.add(pop());
        }
    }
```

- 戻り値型として、境界ワイルドカード型を使わない。

```java
public static <E> Set<E> union(Set<? extends E> e1, Set<? extends E> e2)
```

# 項目３２　ジェネリックスと可変長引数に注意して組み合わせる

- 可変長引数メソッドと、ジェネリックスはJava5で同時に追加されたが、それらは協調しない。
- ヒープ汚染は、パラメータ化された型の変数が、その型では無いオブジェクトを参照している場合に発生する。
- ジェネリックの可変長配列パラメータに値を保存するのは安全では無い。
- ジェネリック可変長パラメータは型安全ではないが許されている。
- ジェネリック可変長パラメータを持つメソッドを書くなら、最初にそのメソッドが型安全になるようにする。使いやすいように、`@SafeVarags`アノテーションを付ける。

# 項目３3　型安全な異種コンテナを検討する

- mapで、キーをClass、値をinstanceとして格納するコンテナ。

```java:型安全な異種コンテナパターン
public class Favorites {
    private Map<Class<?>, Object> favorites = new HashMap<>();

    public <T> void putFavorite(Class<T> type, T instance) {
        favorites.put(Objects.requireNonNull(type), instance);
    }

    public <T> T getFavorite(Class<T> type) {
        return type.cast(favorites.get(type));
    }
}

// 呼び出し例
Favorites f = new Favorites();
f.putFavorite(String.class, "Java");
f.putFavorite(Integer.class, 0xcafebabe);
f.putFavorite(Class.class, Favorites.class);
String favoriteString = f.getFavorite(String.class);
int favoriteInteger = f.getFavorite(Integer.class);
Class<?> favoriteClass = f.getFavorite(Class.class);
```