LoginSignup
4
5

More than 3 years have passed since last update.

Effective Java 第3版 第5章ジェネリックス

Last updated at Posted at 2019-06-09

Java中級者以上の必須本である、Effective Java 第3版に Kindle版が出たので、まとめる。

前:Effective Java 第3版 第4章クラスとインタフェース
次:Effective Java 第3版 第6章enumとアノテーション

用語

  • パラメータ化された型・・・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

項目26 原型を使わない

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

項目27 無検査警告を取り除く

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

項目28 配列よりもリストを選ぶ

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

項目29 ジェネリック型を使う

ジェネリクス未使用
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);
        }
    }
}
  • ObjectEに変更する。
ジェネリクスへの書き換え例
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);
        }
    }
}

項目30 ジェネリックメソッドを使う

  • ジェネリックメソッドの、型パラメータを宣言する型パラメータのリスト<E>は、メソッド修飾子とメソッドの戻り値型の間に入る。
  • 境界ワイルドカード型を使うことでもっと柔軟にできる。
ジェネリックメソッド
    public static <E> Set<E> union(Set<E> s1, Set<E> s2) {
        Set<E> result = new HashSet<>(s1);
        result.addAll(s2);
        return result;
    }

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

境界ワイルドカードを使った例
    // 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());
        }
    }
  • 戻り値型として、境界ワイルドカード型を使わない。
public static <E> Set<E> union(Set<? extends E> e1, Set<? extends E> e2)

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

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

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

  • mapで、キーをClass、値をinstanceとして格納するコンテナ。
型安全な異種コンテナパターン
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);
4
5
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
4
5