제네릭은 Set<E>, Map<K,V> 등의 컬렉션과 ThreadLocal, AtomicReference등의 단일원소 컨테이너에도 쓰인다. 이런 모든 쓰임에서 매개변수화되는 대상은 컨테이너 자신이다.

 

에컨대 Set에는 원소의 타입을 뜻하는 단 하나의 타입 매개변수만 있으면되며, Map에는 키와 값의 타입을 뜻하는 2개만 필요한 식이다.

그러나 떄떄로 더 유연한 수단이 필요할 때가 있다.

 

데이터베이스의 행은 임의 개수의 열을 가질 수 있는데 모든 열을 타입 안전하게 이용할 수 있다면 멋질것이다.

컨테이너 대신 키를 매개변수화한 다음, 컨테이너에 값을 넣거나 뺄 때 매개변수화한 키를 함께 제공하면 된다.

 

이렇게 하면 제네릭 타입 시스템이 값의 타입이 키와 같음을 보장하고 이러한 설계 방식을 타입 안전 이종 컨테이너 패턴이라 한다.

public class Favorite {
    private Map<Class<?>, Object> favorites = new HashMap<>();

    public <T> void putFavorite(Class<T> type, T instance) {
        favorites.put(Objects.requireNonNull(type), type.cast(instance));
    }
    public <T> T getFavorite(Class<T> type) {
        return type.cast(favorites.get(type));
    }
}

Favorites가 사용하는 private Map 변수인 favoirtes의 타입은 Map<Class<?>, Object>이다.

와일드카드 타입이 중첩(nested)되어 있어서 모든 키가 서로 다른 매개변수화 타입일 수 있다.

예를 들어 Class<String>, Class<Integer>와 같은 식으로 키를 넣을 수 있다.

하지만 Favorites클래스에는 알아두어야할 제약이 두가지 있다.

  • 첫 번째, 악의적인 클라이언트가 class 객체를 로타입으로 넘기면 Favorites 인스턴스의 타입 안전성이 쉽게 깨진다. 하지만 이럴경우 컴파일 에러가 뜰 것이다.
  • 두 번째, 실체화불가 타입(Item 28) 에는 사용할수없다는 것이다. 다시 말해, 즐겨찾는 String이나 String[]은 저장할수 있어도 즐겨 찾는 List은 저장할 수 없다.

정리

컬렉션 API로 대표되는 일반적인 제네릭 형태에서는 한 컨테이너가 다룰 수 있는 타입 매개변수의 수가 고정되어 있다. 하지만 컨테이너 자체가 아닌 키를 타입 매개변수로 바꾸는 이런 제약이 없는 타입 안전 이종 컨테이너를 만들 수 있다. 타입 안전 이종 컨테이너는 Class를 키로 쓰며, 이런 식으로 쓰이는 Class 객체를 타입 토근이라 한다. 또한 직접 구현한 키 타입도 쓸 수 있다. 예컨대 데이터베이스의 행을 표현한 DatabaseRow타입에는 제네릭 타입인 Column<T>를 키로 사용할 수 있다.

+ Recent posts