스터디/이펙티브 자바

[Effective Java] Item33. 타입 안전 이종 컨테이너를 고려

📝 작성 : 2022.06.19  ⏱ 수정 : 
728x90

제네릭은 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 메서드를 통해 형변환을 합니다.

 

타입이 일치하면 그대로 반환하고 아니면 ClassCastException을 발생

 

지금의 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>은 저장할 수 없습니다.


첫 번째 제약과는 반대로 완벽한 우회방법은 없습니다. 다만 슈퍼 타입 토큰으로 어느정도 해결이 가능하지만 완벽하게 해결할 수는 없습니다.

 
 
수퍼타입토큰 참고

닐 개프터(Neal Gafter)의 블로그

토비의 봄 - 수퍼타입토큰1

토비의 봄 - 수퍼타입토큰2

토비의 봄 - 수퍼타입토큰3

반응형