타입 안전 이종 컨테이너를 고려하라
•
제네릭을 통한 컬렉션과 컨테이너를 사용할 때, 매개변수화 되는 대상이 컨테이너 자신이기 때문에 하나의 컨테이너에서 매개변수화 할 수 있는 타입의 수가 제한된다. 이는 컨테이너의 설계에 맞는 것으로 문제가 되지 않지만, 더 유연하게 사용하고 싶은 경우가 있다.
•
컨테이너에 매개변수화 한 키를 통해 넣거나 빼서 사용하는 방식으로 value의 타입이 키와 같음을 보장한다. 이러한 설계 방식을 타입 안전 이종 컨테이너 패턴(type safe heterogeneous container pattern)이라 한다.
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));
}
}
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);
Integer favoriteInteger = f.getFavorite(Integer.class);
Class<?> favoriteClass = f.getFavorite(Class.class);
}
Java
복사
•
이와 같이 타입별로 인스턴스를 저장하고 검색할 수 있는 이종 컨테이너를 만들어서 사용하면 유연한 설계를 할 수 있다.
•
Class<?>의 비한정적 와일드카드를 통해 모든 Class 타입을 받을 수 있다.
•
다만 여기서 내부 컨테이너의 값 타입이 Object인 것은 모든 값이 키 값으로 받아온 클래스와 일치한다고 보증하지 않는다는 것이다.
•
Object 타입의 값을 아래의 Class의 cast 메서드를 통해 동적으로 형변환하여 반환한다.
public class Class<T> {
T cast(Object obj);
}
Java
복사
•
단순히 인수를 그냥 반환하기만 하는데 타입 캐스팅을 쓰지않고 cast를 사용하는 이유는 T로 비검사 형변환하는 손실 없이 타입 안전하게 만들 수 있기 때문이다.
타입 이종 컨테이너의 제약
1.
악의적으로 클라이언트에서 Class 객체를 제네릭이 아닌 로 타입으로 넘기면 타입 안전성이 깨질 수 있다는 것이다. 하지만 이는 인수로 받은 값의 타입이 Class 객체의 타입과 같은 지 동적 형변환을 통해 확인하면 된다.
public <T> void putFavorite(Class<T> type, T instance) {
favorites.put(Object.requireNonNull(type), type.cast(instance));
}
Java
복사
2.
이러한 이종 컨테이너 패턴은 실체화 불가 타입에는 사용할 수 없다. instance에 String[]을 저장할 순 있어도, List<String>을 저장하는 것은 불가능하다. List<String>과 List<Integer>는 List.class라는 같은 Class 객체를 공유하기 때문에 List.class를 통해서만 넣을 수 있고, 이렇게 넣게되면 컨테이너 내부가 아수라장이 될 것이다.