Item 33: Consider typesafe heterogeneous containers

# 33.型安全な異種コンテナーを検討する

  ```Set<E>``` や```Map<K,V>``` といったcollections、また、```ThreadLocal<T>``` や```AtomicReference<T>``` といった要素を格納するコンテナでは、型パラメータの数が固定されている（Setだと1つ、Mapだと2つ）。
  型パラメータの数が固定されていることは、大概の場合において、ユーザが望んでいることだが、時にはもっと柔軟性が必要となるときがある。こういった時の対応方法としては、コンテナをパラメータ化するのではなく、キーをパラメータ化する。
  このアプローチのシンプルな例として、クライアントに任意で複数の型のインスタンスを格納、検索をさせる```Favorites``` クラスを考える。
  ```Favorites``` クラスのAPIは以下のよう。Mapのキーが```Class<T>``` になっている。
  
```java
// Typesafe heterogeneous container pattern - API
public class Favorites {
    public <T> void putFavorite(Class<T> type, T instance);
    public <T> T getFavorite(Class<T> type);
}
```
以下のコードを実行すると、

```java
// Typesafe heterogeneous container pattern - client
public static void main(String[] args) {
    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);
    System.out.printf("%s %x %s%n", favoriteString,
        favoriteInteger, favoriteClass.getName());
}
```
予想通り、Java cafebabe パッケージ名$Favorites と出る。
```Favorites```インスタンスはタイプセーフであり、異種（heterogeneous：普通のMapと異なり、全てのキーの型が異なる)であるため、```Favorites```のようなクラスは、typesafe heterogeneous container と呼ばれる。
  実装は以下のよう。

```java
// Typesafe heterogeneous container pattern - implementation
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));
    }
}
```
  Mapのキーが```Class<?>``` となっており、どんな型でもキーに取ることができる。
  また、Mapのバリューは```Object```である。つまり、Mapにおいてはキーとバリューの型の結びつきは保証されていない。
  ```putFavorites``` メソッドにおいては、単純にClassオブジェクトとインスタンスをMapに入れ込むのみ。
  ```getFavorites``` メソッドは技巧的で、Classオブジェクトをキーに、Mapから取ってきたバリューを、Classの```cast``` メソッドで動的に```T```を返すようにキャストしている。
  ```cast``` メソッドは、引数に取ったインスタンスが自身と同じ型なら、自身の型にキャストして戻す、ということをしている。

  ```Favorites``` クラスには2つ気を付けるべきことがある。
  1つ目は、悪意のあるクライアントは、rawタイプなClassオブジェクトを使って```Favorites``` クラスのタイプセーフを破ってくるということだ。これを防ぐためには、```putFavorite``` メソッドにおいて、格納するインスタンスが本当にキーと同じ型であるかを、```cast``` メソッドを使って確かめればよい。

```java
// Achieving runtime type safety with a dynamic cast
public <T> void putFavorite(Class<T> type, T instance) {
    favorites.put(type, type.cast(instance));
}
```
これについては、checkedSet, checkedList, checkedMap などでも同じ技術が使われている。これらのラッパークラスは、ジェネリックスとrawタイプが混在したアプリケーションにおいて、クライアントコードから不正な要素の追加をしているところを見つけ出すのに有用である。
  2つ目は、```Favorites``` クラスはnon-reifiableな型（Item28）を利用できない。つまり、```String``` や、```String[]``` を格納することはできるが、```List<String>``` を格納することはできない。これに関しては、満足いく回避策はない。

  上述の```Favorites``` クラスでは型に境界はなく、```getFavorite``` も```putFavorite``` もあらゆる型を受け入れていた。もし、受け入れる型に制限を加えたいのであれば、境界のある型パラメータ（Item30）や境界ワイルドカード（Item31）を使える。

  アノテーションAPI（Item39）では境界のある型パラメータの拡張的な使い方をしている。例として、実行時にアノテーションを読むメソッドを考える。このメソッドは```AnnotatedElement``` インターフェースにあり、クラス、メソッド、フィールドなどの要素を代表した、リフレクティブ型（reflective types）に実装される。（**リフレクティブ型？？**）

```java
public <T extends Annotation>
    T getAnnotation(Class<T> annotationType);
```
  引数である```annotationType``` はアノテーションの型を表すが、境界のある型変数が使われている。このメソッドは、引数で受け取った型のアノテーションがあればそれを返し、なければnullを返す。重要なのは、アノテーションを付与された要素は、キーがアノテーション型である、typesafe heterogeneous container であるということである。
  仮に```Class<?>```という型のオブジェクトを持っていたとして、引数に境界のある型パラメータを求めるようなメソッド、つまり、今回の```getAnnotation``` に渡すとする。その場合には、```Class<T extends Annotation>``` へのキャストをすると思うが、このキャストはコンパイル時にワーニングが出る（Item27）。
  このような時には、```asSubclass``` メソッドを用いる。このメソッドは、自身が引数に取った```Class``` オブジェクトのサブクラスであれば、自身の```Class``` オブジェクトを戻し、そうでなければ```ClassCastException``` をスローするというものである。
  結果、以下の実装となる。

```java
// Use of asSubclass to safely cast to a bounded type token
static Annotation getAnnotation(AnnotatedElement element, String annotationTypeName) {
    Class<?> annotationType = null; // Unbounded type token
    try {
        annotationType = Class.forName(annotationTypeName);
    } catch (Exception ex) {
        throw new IllegalArgumentException(ex);
    }
    return element.getAnnotation(annotationType.asSubclass(Annotation.class));
}
```