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);
}
}
}
-
Object
をE
に変更する。
ジェネリクスへの書き換え例
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);