Item 33. 타입 안전 이종 컨테이너를 고려하라

생성일
2023/07/25 05:52
챕터
5장 - 제네릭

타입 안전 이종 컨테이너를 고려하라

제네릭을 통한 컬렉션과 컨테이너를 사용할 때, 매개변수화 되는 대상이 컨테이너 자신이기 때문에 하나의 컨테이너에서 매개변수화 할 수 있는 타입의 수가 제한된다. 이는 컨테이너의 설계에 맞는 것으로 문제가 되지 않지만, 더 유연하게 사용하고 싶은 경우가 있다.
컨테이너에 매개변수화 한 키를 통해 넣거나 빼서 사용하는 방식으로 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를 통해서만 넣을 수 있고, 이렇게 넣게되면 컨테이너 내부가 아수라장이 될 것이다.