[이펙티브자바] item33. 타입 안전 이종 컨테이너를 고려하라

wally·2022년 6월 6일
0

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

정의

타입 안전 이종 컨테이너 패턴(type sage heterogeneous container pattern) : 컨테이너 대신 키를 매개변수화한 다음, 컨테이너에 값을 넣거나 뺄 때 매개변수화된 키를 함께 제공하는 방식. 이런경우 제네릭 타입 시스템이 값의 타입이 키와 같음을 보장해 준다.

예시1) Favorites class (타입별로 즐겨 찾는 인스턴스 저장 후 검색)

  • 각 타입의 Class 객체를 매개변수화 한 키 역할로 사용한다.(class의 클래스가 제네릭이기 때문에 가능)
    • 클래스의 리터럴 타입은 Class 가 아닌 Class<T> 이다.
      • String.class 의 타입은 Class<String> 이다.
      • Integer.class 의 타입은 Class<integer> 이다.

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

2. Favorites class

1. 코드 설명

    public static void main(String[] args) {
        Favorites f = new Favorites();
        f.putFavorite(String.class, "Java");
        f.putFavorite(Integer.class, 0x123abcd);
        f.putFavorite(Class.class, Favorites.class);

        String favoriteString = f.getFavorite(String.class);
        int favoriteInteger = f.getFavorite(Integer.class);
        Class<?> favoriteClass = f.getFavorite(Class.class);

        System.out.printf("%s %x %s%n",
                favoriteString, favoriteInteger, favoriteClass.getName());
    }
  • Favorites 인스턴스는 타입 안전하다. String 요청 시 Integer 반환하지 않는다 즉 타입 안전 이종 컨테이너 이다.

public class Favorites {

    private Map<Class<?>, Object> favorite = new HashMap<>();

    public <T> void putFavorite(Class<T> type, T instance) {
        favorite.put(Objects.requireNonNull(type), instance);
    }

    public <T> T getFavorite(Class<T> type) {
        return type.cast(favorite.get(type));
    }
}
  • Map<Class<?>, Object> 는 키가 와일드카드 타입니다.
  • 값 타입이 단순히 Object 이다.
    • 키와 값 사이의 타입 관계를 보증하지 않는다.
    • 즉 모든 값이 키로 명시한 타입임을 보장하지 않는다.
  • 하지만 Put 메서드는 <T> 를 통해 ObjectClass<?> 와 같은 타임임을 보장한다.
    • 런타임에서 타입제거가 되기 때문에 타입링크 정보는 사라진다. 그저 Class<?>Object 관계에 포함되는것이다.(하지만 코드 설계자는 Put 메서드를 통해 타입이 같음을 알 수 있다.
  • get 메서드는 주어진 Class 객체에 해당하는 값을 꺼낸다. 반환 객체의 타입은 Object 로 잘못된 컴파일타임 타입을 가지고 있다.
    • 우리는 이것을 T 로 바꿔 반환해야 한다.
  • Classcast 메서드를 사용해 객체 참조를 Class 객체가 가리키는 타입으로 동적 형변환 해준다.
  • 이를 통해 T 로 비검사 형변환하는 손실 없이도 Favorites 를 타입 안전하게 만들 수 있다.

2. 제약 조건

  1. 악의적인 클라이언트가 Class 객체를 로 타입으로 넘기면 타입 안전성이 깨진다. 하지만 컴파일 시 비검사 경고가 뜰것이다.

  2. 실체화 불가 타입에는 사용할 수 없다.

  • 즐겨 찾는 String 이나 String[] 은 저장 가능하지만 List<String> 은 저장불가하다.(컴파일 되지 않는다)
    • List<String>Class 객체를 얻을 수 없기 떄문이다.
    • List<String>.class 작성 시 문법 오류가 난다.
    • List<String>List<Integer>List.class 라는 같은 Class 를 공유하므로 만약 실체화 불가 타입을 허용한다면 키값이 중볻되는 아수라장이 될것이다.

3. 한정적 타입 토큰

Favorites 가 사용하는 타입 토큰은 비한정적이다.

  • get 과 put 은 어떤 CLass 객체든 받아들인다.

이를 한정적 타입 토큰으로 제한할 수 있다.

  • 한정적 타입 매개변수나 한정적 와일드 카드 사용하면 된다.
public <T extends Annotation> T getAnNotation(Class<T> annotationType);
  • 입력 매개변수를 <T> 가 아닌 <T extends Annotation> 으로 제한을 준다.

만약 Class<?> 타입의 객체가 있고, 이를 한정적 타입 토큰을 받는 메서드에 넘기려면 어떻게 해야 할까?

  • 객체를 Class<? extends Annotation) 으로 형변환 가능하지만 비검사이므로 컴파일 경고가 뜬다.
    • Class 가 형변환을 안전하고 동적으로 수행하는 인스턴스 메서드를 제공한다.
      • 호출된 인스턴스 자신의 Class 객체를 인수가 명시한 클래스로 형변환하는 asSubClass 메서드롤 사용하면 된다.
        • 형변환 된다 : 이 클래스가 인수로 명시한 클래스의 하위 클래스이다.
        • 형변환 실패 : ClassCastException
static Annotation getAnnotation(
    AnnotatedElement 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);
    );
}

참고 도서 및 사이트

이펙티브 자바 - 조슈아 블로크
https://absorbed-cheek-029.notion.site/item33-009fd935813c4247a028f34a5ba522b0

profile
클린코드 지향

0개의 댓글