Effective Java - 제네릭(7)

SeungHyuk Shin·2021년 10월 16일
0

Effective Java

목록 보기
19/26
post-thumbnail

[아이템 33]. 타입 안전 이종 컨테이너를 고려하라


1. 타입 안전 이종 컨테이너 패턴

매개변수화 되는 대상은 원소가 아닌 컨테이너 자신이다. Set가 있을 때 매개변수화 되는 것은 Integer가 아니라 List이다.

하나의 컨테이너에서 매개변수화 할 수 있는 타입의 수가 제한된다.

이보다 유연한 수단 : 타입 안전 이종 컨테이너 패턴

타입 안전 이종 컨테이너 패턴 (type safe heterogeneous container pattern)
= 컨테이너 대신 키를 매개변수화 한 다음, 컨테이너에 값을 넣거나 뺄대 매개변수화 한 키를 함께 제공한다.

각 타입의 Class 객체를 매개변수화한 키 역할로 사용한다 : 이때 class 리터럴의 타입은 Class이다.

public class Favorites{ // 타입 이종 컨테이너 추상화
  public <T> void putFavorite(Class<T> type, T instance);
  public <T> T getFavorite(Class<T> type)
}

타입토큰 : 컴파일 타임 정보와 런타임 타입 정보를 알아내기 위해 메서드들이 주고받는 class 리터럴

public class Favorites { // 타입 이종 컨테이너 구현

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));
  }
}

여기서 와일드 카드 타입으로 put 할수 없다고 생각할 수 있지만, 이때 키가 와일드 카드 타입이기 때문에 넣을 수 있다.

Map의 값이 Object를이기 대문에 Class의 cast 메서드를 사용해 동적 형변환한다.
이때 cast 메서드에서 제네릭의 이점을 완벽히 사용한다 : 비검사 형변환 없이도 Favorites를 타입 안전하게 한다.

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

악의적인 클라이언트가 Class 객체를 (제네릭이 아닌) 로 타입(아이템 26)으로 넘기면 Favorite 인스턴스의 타입 안전성이 쉽게 깨진다. 하지만 이는 클라이언트 코드에서 컴파일할 때 비검사 경고가 뜰 것이다.Favorites가 타입 불변식을 어기는 일이 없도록 보장하려면 putFavorite 메서드와 같이 instance의 타입이 type으로 명시한 타입과 같은지 확인하면 된다.

java.util.Collections에는 checkedSet, checkedList, checkedMap 같은 메서드가 있는데 바로 이 방식을 적용한 컬렉션 래퍼들이다.Favorites 클래스의 두 번째 제약은 실체화 불가 타입에는 사용할 수 없다는 것이다.

다시 말해, 즐겨 찾는 String이나 String[]은 저장할 수 있어도 즐겨 찾는 List은 저장할 수 없다.List을 저장하려는 코드는 컴파일되지 않을 것이다. List용 Class 객체를 얻을 수 없기 때문이다.

List.class라고 쓰면 문법 오류가 난다. List과 List는 List.class라는 객체를 공유하기 때문이다.이는 한정적 타입 토큰을 활용하면 가능하다. 한정적 타입 토큰이란 단순히 한정적 타입 매개변수나 한정적 와일드카드를 사용하여 표현 가능한 타입을 제한하는 타입 토큰이다.애너테이션 API(아이템 39)는 한정적 타입 토큰을 적극적으로 사용한다.

  public <T extends Annotation> T getAnnotation(Class<T> annotationType);

여기서 annotationType인수는 애너테이션 타입을 뜻하는 한정적 타입 토큰이다. 이 메서드는 토큰으로 명시한 타입의 에너테이션이 대상 요소에 달려 있다면 그 애너테이션을 반환하고 없다면 null을 반환한다. 즉, 애너테이션된 요소는 그 키가 애너테이션 타입인, 타입 안전 이종 컨테이너이다.

static Annotation getAnnotation(AnnotationElement element, String annotationTypeName){
Class<?> annotationType = null; //바한정적 타입 토큰
try{
  annotationType = Class.forName(annotationTypeName);
}catch (Exception ex){
  throw new IllegalArgumentException(ex);
}
return element.getAnnotation(annotationType.asSubClass(Annotation.class))
}

컬렉션 API로 대표되는 일반적인 제네릭 형태에서는 한 컨테이너가 다룰 수 있는 타입 매개변수의 수가 고정되어 있다. 하지만 컨테이너 자체가 아닌 키를 타입 매개변수로 바꾸면 이런 제약이 없는 타입 안전 이종 컨테이너를 만들 수 있다. 타입 안전 이종 컨테이너는 Class를 로 쓰며, 이런 식으로 쓰이는 Class 객체를 타입 토큰이라 한다.

0개의 댓글