#ジェネリクス
##項目23 新たなコードで原型を使用しない
//悪い例
private final Collection stamps =...;
//良い例
private final Collection<Stamp> stamps = ...;
}
原型がだめな理由
- 誤ったオブジェクトを挿入→実行時にClassCastException
ジェネリク型の利点
- 誤ったオブジェクトを挿入→コンパイル時にエラー
- 要素を取り出す際にキャストする必要がない
###要素がわかっていない場合は非境界ワイルドカードを利用する
ジェネリックを利用したいけど、パラメータが何かわからない
→非境界ワイルドカードを利用する
//悪い例
static int numElementsInCommon(Set s1, Set s2){
int result = 0;
for (Object o1 : s1)
if (s2.contains(o1))
result ++;
return result;
}
- Setだったらどんな要素でも挿入可
- Set>にはいかなる要素も挿入不可
###結論
原型を利用せずにジェネリック型を利用する
##項目24 無検査警告を取り除く
(経験が浅いうちは)ジェネリックを多用すると無検査キャスト警告が多発します
- 取り除くことが可能なら無検査警告を取り除く
- 警告を取り除くことができない、でも型安全であることは明確、@SuppressWarnings("unchecked")アノテーションで警告を抑制する
- @SuppressWarnings("unchecked")アノテーションを利用する場合、スコープはできるだけ小さくする
- 安全である理由をコメントとして残す
##項目25 配列よりリストを選ぶ
###配列
- 共変
SubがSuperのサブタイプならSub[]がSuper[]のサブタイプ - 具象化
実行時に型要素を既知かつ強制
###リスト
- 不変
- イレイズ
コンパイル時のみ要素の型を強制、実行時には型情報を破棄
//配列の場合 実行時に失敗
Object[] objectArray = new Log[1];
objectArray[0] = "I don't fit in";
//リストの場合 コンパイル時に失敗
List<Object> ol = new ArrayList<Long>();
ol.add("I don't fit in");
リストはコンパイル時の型安全性を提供
###結論
配列を利用せずにリストを利用する
###ジェネリック型の配列、パラメータ化された型の配列、型パラメータの配列は許可されていない
new List<E>[], new List<String>[], new E[]
##項目26 ジェネリック型を使用する
###自作のクラスをジェネリックにする利点
- 型安全性を保証
- クライアントが自作クラスを利用時に明示的なキャストが不要
###配列で実現されているクラスをジェネリックスに変換する場合
例) 項目6 のStack
をStack<E>
に変換
####2つの方法
- Object配列を生成して、ジェネリック型配列へキャスト
- 型パラメータ配列のフィールドをObject[]にする
##項目27 ジェネリックメソッドを使用する
###クラスと同様メソッドもジェネリックメソッドに変更可能
//原型利用
public static Set union (Set s1, Set s2){
Set result = new HashSet(s1);
result,addAll(s2);
return result;
}
//ジェネリックメソッド
public static <E> Set<E> union (Set<E> s1, Set<E> s2){
Set<E> result = new HashSet<E>(s1);
result,addAll(s2);
return result;
}
上記のジェネリックメソッドの制限
- 3つのSetの型が全て同じでなければならない
→コンパイラが型推論できる
インスタンス生成例
Map<String, List<String>> anagrams = new HashMap<String, List<String>>()
以下を利用すれば簡単になる
//ジェネリックstaticファクトリーメソッド public static <K,V> HashMap<K,V> newHashMap(){ return new HashMap<K,V> }
繰り返しの宣言不要
Map<String, List<String>> anagrams = newHashMap()
##項目28 APIの柔軟性向上のために境界ワイルドカードを使用する
//ワイルドカード未使用
public void pushAll (Iterable<E> src){
for(E e : src);
push(e);
}
//ワイルドカード使用
public void pushAll (Iterable<? extends E> src){
for(E e : src);
push(e);
}
//ワイルドカード未使用
public void popAll (Collection<E> dst){
while(!isEmpty());
dst.add(pop());
}
//ワイルドカード使用
public void pushAll (Collection<? super E> dst){
while(!isEmpty());
dst.add(pop());
}
##項目29 型安全な異種コンテナーを検討する
What?
ひとつのコンテナで、複数の型の要素を、型安全を保証しつつ取り扱う。
Why?
単一要素を取り扱うコンテナではまかないきれない、柔軟性が必要な場合がある。
Example?
データベースの行は任意の数の多くの列を持っており、型安全な方法でそれらすべての列をアクセスしたい。
呼び出し側
//型安全な異種コンテナーパターン
public Class Favorites {
public <T> void putFavorites (Class<T> type, T instance);
public <T> get Favorites<Class<T> type>;
}
クライアント側
public static void main (String[] args) {
Favorites f = new Favorites();
f.putFavorite(String.class, "Java");
f.putFavorite(Integer.class, 0xcafebade);
f.putFavorite(Class.class, Favorite.class);
String favoriteString = f.getFavorite(String.class);
int favoriteInteger = f.getFavorite(Integer.class);
Class<?> favoriteClass = g.get(Class.class);
}
実装
public class Favorites {
private Map<Class<?>, Object> favorites =
new HashMap<Class<?>, Object>();
public <T> void put Favorite(Class<T> type, T instance){
if (type == null)
throw NullPointerException ("Type is null");
favorites.put(type, instance);
public <T> getFavorite(Class<T>, type) {
return type.cast(favorites.get(type));
}
}
###制限
- 原型
- 具現化不可能型
###型を制御
What
メソッドに渡す型を制限したい場合がある。
How
境界型トークンを導入する。
境界型パラメータか、境界ワイルドカードを利用する。
まとめ
ひとつのコンテナで、複数の型を安全に管理できる。
そのための方法は、キーにClassオブジェクト(=型トークン)を使用することである。