제네릭은 Set<E>
, Map<K, V>
등의 컬렉션과 TreadLocal<T>
등의 단일원소 컨테이너에도 흔히 사용됩니다.
이런 모든 쓰임에서 매개변수화 되는 대상은 원소가 아닌 컨테이너 자신입니다.
따라서 하나의 컨테이너에서 매개변수화 할 수 있는 타입의 수가 제한됩니다.
Set
에는 단 하나의 타입 매개변수만 허용되며, Map
에는 key와 value에 해당하는 두 개의 타입 매개변수만 허용됩니다.
이런 타입 매개변수의 갯수를 유연하게 하는 방법이 있습니다.
컨테이너 대신 키를 매개변수화한 다음 컨테이너에 값을 넣거나 뺄 때 매개변수화한 키를 함께 제공하는 것입니다.
이러한 설계 방식을 타입 안전 이종 컨테이너 패턴
이라고 합니다.
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));
}
}
위의 Favorites 클래스는 타입 안전합니다. getFavorite(String.class);
를 하면 무조건 String을 반환합니다.
또한 넣을 수 있는 타입이 제각각이라, 일반적인 맵과 달리 여러가지 타입의 원소를 담을 수 있습니다.
따라서 Favorites 클래스는 타입 안전 이종 컨테이너입니다.
Favorites 클래스에 대해 좀 더 들여보면 favorites의 타입은 Map<Class<?>, Object>
입니다.
이때 맵이 아닌 key의 타입이 와일드카드 타입이기 때문에 모든 key가 서로 다른 매개변수화 타입일 수 있습니다.
또한, favorites의 value의 타입은 Object
입니다. 즉 key와 value 사이의 타입 관계를 보증하지 않습니다.
그렇기 때문에 getFavorite 메서드에서 cast
메서드를 통해 형변환을 합니다.
지금의 Favorites 클래스를 사용하는데 두 가지 제약이 있습니다.
첫 번째, Class 객체를 raw 타입으로 넘기면 타입 안전성이 깨집니다.
이를 예방하기 위해 putFavorite(Class<T> type, T instance)
메서드에서 instance의 타입이 type으로 명시한 타입과 같은지 확인하는 코드를 추가해 줍니다.
public <T> void putFavorite(Class<T> type, T instance) {
favorites.put(Objects.requireNonNull(type), type.cast(instance));
}
두 번째 제약은 실체화 불가 타입에는 사용할 수 없다는 것입니다. 즉 String
, String[]
은 저장 가능하지만 List<String>
은 저장할 수 없습니다.
첫 번째 제약과는 반대로 완벽한 우회방법은 없습니다. 다만 슈퍼 타입 토큰으로 어느정도 해결이 가능하지만 완벽하게 해결할 수는 없습니다.
수퍼타입토큰 참고
'스터디 > 이펙티브 자바' 카테고리의 다른 글
[Effective Java] Item35. ordinal 대신 인스턴스 필드 (0) | 2022.06.26 |
---|---|
[Effective Java] Item34. int 대신 Enum (0) | 2022.06.19 |
[Effective Java] Item32. 제네릭과 가변인수를 함께 쓸 때는 신중하게 (0) | 2022.06.18 |
[Effective Java] Item31. 한정적 와일드카드를 사용해 API 유연성을 높여라 (0) | 2022.06.18 |
[Effective Java] Item30. 이왕이면 제네릭 메서드로 (0) | 2022.06.18 |