일반적인 제네릭의 타입 매개변수의 개수는 고정되어 있는데, 더 유연한 수단이 필요할 때가 있다. 이럴 경우, 타입 안전 이종 컨테이너를 고려해보자.
public class Favorites {
private Map<Class<?>, Object> favorites = new HashMap<>();
public <T> void putFavorite(Class<T> type, T instance) {
//cast 메서드를 사용해 타입 안정성을 보장한다!
favorites.put(Objects.requireNonNull(type), type.cast(instance));
}
public <T> T getFavorite(Class<T> type) {
//cast 메서드를 사용해 타입 안정성을 보장한다!
return type.cast(favorites.get(type));
}
}
@Test
public void 이종_타입_컨테이너() {
Favorites f = new Favorites();
f.putFavorite(String.class, "스트링");
f.putFavorite(Integer.class, 1);
f.putFavorite(Long.class, 1L);
assertEquals(f.getFavorite(String.class), "스트링");
assertEquals(f.getFavorite(Integer.class), new Integer(1));
assertEquals(f.getFavorite(Long.class), new Long(1L));
}
@Test
public void 이종_타입_컨테이너_실체화_불가_타입_문제() {
Favorites f = new Favorites();
f.putFavorite(String[].class, new String[1]);
//컴파일 에러 발생! 지원하지 않는 타입
// f.putFavorite(List<String>.class, new ArrayList<>());
}
Class를 얻기 위한 2가지 방법
- ParameterizedType를 사용
- TypeFactory를 사용
//슈퍼타입을 사용하여 타입안정성을 보장한다!
public <T> void putFavorite(TypeReference<T> valueTypeRef, T instance) {
// ParameterizedType로 캐스팅하여 Class<T>를 얻는다!
Class<T> tClass = ((Class<T>) ((ParameterizedType) valueTypeRef.getType()).getRawType());
favorites.put(Objects.requireNonNull(tClass), Objects.requireNonNull(instance));
}
//슈퍼타입을 사용하여 타입안정성을 보장한다!
public <T> T getFavorite(TypeReference<T> valueTypeRef) {
// TypeFactory를 이용해서 Class<T>를 얻는다!
Class<T> rawClass = (Class<T>) TypeFactory.type(valueTypeRef.getType()).getRawClass();
return rawClass.cast(favorites.get(rawClass));
}
@Test
public void 이종_타입_컨테이너_실체화_불가_타입_슈퍼타입_토큰() {
//given
Favorites f = new Favorites();
// 슈퍼타입으로 넣는다!
f.putFavorite(new TypeReference<List<String>>() {}, Collections.singletonList("슈퍼 타입 토큰"));
// 슈퍼타입으로 꺼낸다!
List<String> strings = f.getFavorite(new TypeReference<List<String>>() {});
//then
assertEquals("슈퍼 타입 토큰", strings.get(0));
}
스프링에서 구현한 슈퍼타입 ParameterizedTypeReference
ParameterizedTypeReference의 생성자를 잠깐 살펴보자.
protected ParameterizedTypeReference() {
Class<?> parameterizedTypeReferenceSubclass = findParameterizedTypeReferenceSubclass(getClass());
Type type = parameterizedTypeReferenceSubclass.getGenericSuperclass();
Assert.isInstanceOf(ParameterizedType.class, type, "Type must be a parameterized type");
ParameterizedType parameterizedType = (ParameterizedType) type; //ParameterizedType로 캐스팅한다!
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
Assert.isTrue(actualTypeArguments.length == 1, "Number of type arguments must be 1");
this.type = actualTypeArguments[0];
}
일반적인 제네릭 형태에서는 한 컨테이너가 다룰 수 있는 타입 매개변수의 수가 고정되어 있다. 하지만, 컨테이너 자체가 아닌 키를 타입 매개변수로 바꾸면 이런 제약이 없는 타입 안전 이종 컨테이너를 만들 수 있다. 타입 안전 이종 컨테이너는 Class를 키로 쓰며, 이런 식으로 쓰이는 Class 객체를 타입 토큰이라 한다. 또한, 직접 구현한 키 타입도 쓸 수 있다.
컨테이너: 타입을 담을 수 있는 형태의 객체 (Set, Map<K, v>, List ..등)