5.33:型安全な異種コンテナを検討する
要点
クラス(型トークン)をキーにして任意の型の値を格納し、取得時に正しい型に安全にキャストして返すコンテナ。コンパイル時に型安全性が保たれるため明示的キャストをほぼ不要にする。
仕組み
- キーとして Class< T >(型トークン)を使う。
- 値は内部的には Object として格納するが、put と get をジェネリックメソッドにすることで呼び出し時に型を結びつける。
- 取得時に Class.cast(...) を使えば実行時チェックも効いて安全。
単純で代表的な実装例
import java.util.*;
public final class Favorites {
private final Map<Class<?>, Object> map = new HashMap<>();
// 値を格納する(型パラメータ T によって型安全性が担保される)
public <T> void putFavorite(Class<T> type, T instance) {
if (type == null) throw new NullPointerException("type");
map.put(type, type.cast(instance)); // cast で実行時チェックも行う
}
// 値を取得する(戻り値は T)
public <T> T getFavorite(Class<T> type) {
return type.cast(map.get(type));
}
}
使い方:
Favorites f = new Favorites();
f.putFavorite(String.class, "Java");
f.putFavorite(Integer.class, 2025);
String s = f.getFavorite(String.class); // 型安全に String が得られる
Integer n = f.getFavorite(Integer.class); // 型安全に Integer が得られる
このパターンが「型安全」である理由:
- putFavorite(Class< T >, T) のシグネチャにより、呼び出し時にコンパイラは T の整合性をチェックする(putFavorite(String.class, 123) はコンパイルエラーになる)。
- 内部 Map は Map< Class< ? >, Object > として保持するが、type.cast(...) により実行時にも正当性をチェックする(ClassCastException はここで起きる可能性はあるが、呼び出し元の型整合性が保たれていれば通常起きない)。
パラメタライズド型(例:List< String >)を扱いたいとき
Class< List< String > > のような型は実行時に List< String > としての情報が失われる(型消去)ため、Class トークンだけでは区別できません。
この場合は java.lang.reflect.Type や TypeToken(Guava の TypeToken 等)をキーにするアプローチを使います:
簡単なアイデア(概念レベル):
Map<java.lang.reflect.Type, Object> map = new HashMap<>();
// 格納は Type をキーにする(Type を得るには new TypeReference<List<String>>(){}.getType() のようなテクニックが必要)
- この場合、格納/取得は通常 @SuppressWarnings("unchecked") を局所化して行うことが多い(コンパイル時チェックは難しいが実行時の type token を利用して整合性を保つ)。
- 実用的には Guava の TypeToken や独自の TypeReference< T > パターンを使うことが多い。
(注:パラメタライズド型を安全に扱うには型トークンの扱いに注意が必要で、単純な Class ベースの実装では対応できない点に注意。)
利点
- コンパイル時の型安全: 呼び出し時に型の不一致が発見できる。
- API がシンプルで使いやすい: 明示的なキャストを呼び出し側で書かなくて良い。
- 汎用コンテナとして便利: アプリケーション設定や「1つだけの値(favorite)」のような用途に向く。
欠点・制約
- パラメタライズド型(List< String > 等)にはそのままでは対応できない(型消去のため)。対応するには Type/TypeToken を使う設計が必要で少し複雑。
- キーは型で固定されるため、同じ型につき一つの値しか持てない(String.class に対して複数の「favorite」を持てない)。
- 滅多にないが misuse があると ClassCastException が起き得る(たとえば raw Class を使う等で型チェックが抜ける場合)。
- 設計が過度に柔軟になると可読性低下:どんな型が入っているか追いにくくなるため、使う場所は限定すべき。
まとめ
- 型安全な異種コンテナは Class(あるいはより汎用なら Type/TypeToken)をキーにして、ジェネリックメソッドで T を結びつけるパターン。
- 明示的キャストが不要になり、呼び出し側は安全に値を入出力できる。
- ただしパラメタライズド型への対応や設計の可読性に気をつける必要がある。