Skip to content

Latest commit

 

History

History
153 lines (119 loc) · 4.97 KB

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

File metadata and controls

153 lines (119 loc) · 4.97 KB

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

일반적인 컨테이너

  • Set, Map<K, V>, ThreadLocal 와 같은 단일 원소 컨테이너
  • 컨테이너 자체가 매개변수화 되었다!

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

public class Favorites {
    public <T> void putFavorite(Class<T> type, T instance);
    public <T> T getFavorite(Class<T> type);
}
  • 컨테이너 대신 키를 매개변수화 한다.
  • 여러 가지 타입의 원소를 담을 수 있다.
  • 제네릭 타입을 통해 값(value)이 키(key)와 같은 타입인 것이 보장된다!

타입 안전 이종 컨테이너 패턴 - 구현

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));
    }
}
  • Class가 비한정적 와일드카드 타입이라 아무것도 넣을 수 없을 것 같지만, Class는 key이기 때문에 넣을 수 있다!
  • type과 instance의 타입 링크 정보는 버려지지만, getFavorite()을 통해 되살아난다.
  • type.cast()는 ClassCastException을 던질 수 있지만, 우리는 클래스가 항상 일치함을 알고 있다.

제약 1. 로 타입 Class를 넘기면 타입 안정성이 깨진다.

f.putFavorite((Class)Integer.class, "String");
int favoriteInteger = f.getFavorite(Integer.class);

[Throw ClassCastException]

public <T> void putFavorite(Class<T> type, T instance) {
    favorites.put(Objects.requireNonNull(type), type.cast(instance));
}
  • 동적 형변환으로 런타임 안정성 확보할 수 있다.
  • 예시) java.util.Collections의 checkedSet, checkedList, checkedMap

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

  • List.class == List.class == List.class
  • 슈퍼 타입 토큰으로 해결할 수도 있지만 완벽하지 않으니 주의해서 사용할 것!
  • 만족스러운 우회로(해결법)은 없다. (조슈아 블로크 주장)

한정적 타입 토큰을 활용하는 법

public <T extends Annotation> T getAnnotation(Class<T> annotationType);
  • AnnotatedElement 인터페이스는 리플렉션으로 대상에 달려있는 어노테이션을 가져온다.
  • annotationType을 키로 갖는 타입 안전 이종 컨테이너 형태로 동작한다.

비한정적 타입 토큰을 한정적 타입 토큰으로 형변환 하는 법

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);
    )
}
  • Class<?> 타입의 객체를 위와 같은 한정적 타입 토큰으로 형변환이 필요할 때
  • 비검사 형변환보다 안전한 Class.asSubclass() 메소드!

슈퍼 타입 토큰

닐 게프터 블로그 : https://gafter.blogspot.com/2006/12/super-type-tokens.html

리플렉션을 사용하더라도 타입 정보를 얻어올 수 없다!

public class SuperTypeToken {

    static class Sup<T> {
        T value;
    }

    public static void main(String[] args) throws Exception {
        Sup<String> s = new Sup<>();
        System.out.println(s.getClass().getDeclaredField("value").getType()); // class java.lang.Object
    }
}

슈퍼 타입 토큰을 사용하면 타입 정보가 바이트코드에 남아있어 리플렉션으로 접근할 수 있다.

public class SuperTypeToken {

    static class Sup<T> {
        T value;
    }

    static class Sub extends Sup<Map<List<?>, Set<String>>> {
    }

    public static void main(String[] args) throws Exception {
        Sub b = new Sub();
        Type t = b.getClass().getGenericSuperclass();
        ParameterizedType pType = (ParameterizedType)t;
        System.out.println(pType.getActualTypeArguments()[0]); // java.util.Map<java.util.List<?>, java.util.Set<java.lang.String>>
    }
}

익명 클래스를 활용한 슈퍼 타입 토큰

public class SuperTypeToken {

    static class TypeReference<T> {
        Type type;

        public TypeReference() {
            Type sType = getClass().getGenericSuperclass();
            if (sType instanceof ParameterizedType) {
                this.type = ((ParameterizedType)sType).getActualTypeArguments()[0];
            } else {
                throw new RuntimeException();
            }
        }
    }

    public static void main(String[] args) throws Exception {
        // TypeReference를 상속받은 익명 클래스를 이용하기 때문에 바디부분{}이 필요하다.
        TypeReference t = new TypeReference<List<String>>(){};
        System.out.println(t.type); // java.util.List<java.lang.String>
    }
}