Item 33 - 타입 안정 이종 컨테이너를 고려하라
서론
- 제네릭은
Set<E>
,Map<K, V>
등의 컬렉션과ThreadLocal<T>
등의 단일 원소 컨테이너에 흔히 쓰인다. - 하나의 컨테이너에서 매개변수화 할 수 있는 타입의 수는 컴파일 타임에 고정된다.
더 유연한 수단이 필요할 때는?
타입 안전 이종 컨테이너
고정된 타입의 객체만 담을 수 있는 컨테이너가 아니라 동적으로 여러 타입을 담을 수 있는 타입 안전한 컨테이너
[예시] 타입별로 즐겨 찾는 인스턴스를 저장하고 검색할 수 있는 Favorites
클래스
1
2
3
4
5
6
7
8
9
10
11
12
// 타입 안전 이종 컨테이너 패턴 - 구현
public class Favorites {
private Map<Class<?>, Object> favorites = new HashMap<>();
public <T> void putFavorites(Class<?> type, T instance) {
favorites.put(Object.requireNonNull(type), instance);
}
public <T> T getFavorite(Class<T> type) {
return type.cast(favorites.get(type));
}
}
- 각 타입의 Class 객체를 매개변수화한 키 역할로 사용하면 된다.
- class의 클래스가 제네릭이므로 가능하다.
- class 리터럴의 타입은
Class
가 아닌Class<T>
다. String.class
의 타입은Class<String>
이고Integer.class
의 타입은Class<Integer>
다.
- 맵의 값 타입이
Object
이므로 모든 타입이 들어올 수 있다.- 이 맵은 값 타입이 키로 설정된 타입과 반드시 일치한다는 것을 보증할 수 없다.
- 하지만,
getFavorite
메서드에서cast
메서드를 통해 보장할 수 있다. - 값 타입이
Object
이므로T
로 바꿔 반환하기 위해cast
메서드로 캐스팅했다. cast
메서드는 주어진 인수가Class
객체가 알려주는 타입의 인스턴스인지 검사한다.- 맞다면 그 인수를 그대로 캐스팅한다.
- 아니면
ClassCastException
을 발생시킨다.
타입 안전 이종 컨테이너의 제약 😑
(1) 악의적인 클라이언트가 Class 객체를 로 타입으로 넘기면 타입 안정성이 깨짐
1
2
f.putFavorite((Class)Integer.class, "hi");
int x = f.getFavorite(Integer.class); // ClassCastException
putFavorite
메서드에서cast
메서드로 타입이 같은지 확인하여 해결한다.
1
2
3
public <T> void putFavorites(Class<T> type, T instance) {
favorites.put(Objects.requireNonNull(type), type.cast(instance));
}
(2) 실체화 불가 타입에는 사용할 수 없음 - (아이템 28)
1
f.putFavorite(List<Integer>.class, "hi"); // 컴파일 오류
List<String>.class
와List<Integer>.class
는List.class
라는 같은 클래스 객체를 공유한다.- 만약
List<String>.class
와List<Integer>.class
를 허용해서 둘 다 똑같은 타입의 객체 참조를 반환한다면Favorites
객체 내부는 아수라장이 된다.
💡 핵심 정리
- 일반적인 제네릭 형태에서는 한 컨테이너가 다룰 수 있는 타입 매개변수의 수가 컴파일 타임에 고정되어 있다.
- 컬렉션 API,
ThreadLocal<T>
,AtomicReference<T>
등의 단일원소 컨테이너
- 컬렉션 API,
- 하지만 컨테이너 자체가 아닌 키를 타입 매개변수로 바꾸면 타입 안전 이종 컨테이너를 만들 수 있다.
- 타입 안전 이종 컨테이너는 Class를 키로 사용하며 이를 타입 토큰이라 한다.
참고 🕶️
수퍼 타입 토큰
- 익명 클래스와 제네릭 클래스 상속을 사용한 타입 토큰
- 닐 게프터의 슈퍼 타입 토큰
- 상속을 사용한 경우 제네릭 타입을 알아낼 수 있다.
- 이 경우에는 제네릭 타입이 제거되지 않기 때문에…
한정적 타입 토큰
- 한정적 타입 토큰을 사용한다면, 이종 컨테이너에 사용할 수 있는 타입을 제한할 수 있다.
1 2
AnnotatedElement.<T extends Annotation> T getAnnotation(Class<T> annotationClass);
asSubclass
메서드- 메서드를 호출하는 Class 인스턴스를 인수로 명시한 클래스로 형변환 한다.
This post is licensed under CC BY 4.0 by the author.