타입 안전 이종 컨테이너: 한 타입의 객체만 담을 수 있는 컨테이너가 아니라 여러 다른 타입 (이종)을 담을 수 있는 타입 안전한 컨테이너.
타입 토큰: String.class 또는 Class<String>
타입 안전 이종 컨테이너 구현 방법: 컨테이너가 아니라 “키”를 매개변수화 하라!
public class Favorites {
private Map<Class<?>, Object> map = new HashMap<>();
public <T> void put(Class<T> clazz, T value) {
this.map.put(Objects.requireNonNull(clazz), clazz.cast(value));
}
public <T> T get(Class<T> clazz) {
return clazz.cast(this.map.get(clazz));
}
public static void main(String[] args) {
Favorites favorites = new Favorites();
favorites.put(String.class, "keesun");
favorites.put(Integer.class, 2);
// favorites.put(List<Integer>.class, List.of(1, 2, 3));
// favorites.put(List<String>.class, List.of("a", "b", "c"));
// List list = favorites.get(List.class);
// list.forEach(System.out::println);
}
}
익명 클래스와 제네릭 클래스 상속을 사용한 타입 토큰
닐 게프터의 슈퍼 타입 토큰 • https://gafter.blogspot.com/2006/12/super-type-tokens.html • https://gafter.blogspot.com/2007/05/limitation-of-super-typetokens.html
상속을 사용한 경우 제네릭 타입을 알아낼 수 있다. 이 경우에는 제네릭 타입이 제 거되지 않기 때문에…
public class GenericTypeInfer {
static class Super<T> {
T value;
}
// 상속을 쓴 경우
static class Sub extends Super<String>{
}
public static void main(String[] args) throws NoSuchFieldException {
// 해당 타입을 알 수 없다.
Super<String> stringSuper = new Super<>();
System.out.println(stringSuper.getClass().getDeclaredField("value").getType());
// 해당 타입을 추론한다.
// 익명 내부 클래스를 통해 클래스를 생성할 필요가 없음
Type type = (new Super<String>(){}).getClass().getGenericSuperclass();
ParameterizedType pType = (ParameterizedType) type;
Type actualTypeArgument = pType.getActualTypeArguments()[0];
System.out.println(actualTypeArgument);
}
}
===
public class Favorites2 {
private final Map<TypeRef<?>, Object> favorites = new HashMap<>();
public <T> void put(TypeRef<T> typeRef, T thing) {
favorites.put(typeRef, thing);
}
@SuppressWarnings("unchecked")
public <T> T get(TypeRef<T> typeRref) {
return (T)(favorites.get(typeRref));
}
public static void main(String[] args) {
Favorites2 f = new Favorites2();
TypeRef<List<String>> stringTypeRef = new TypeRef<>() {};
System.out.println(stringTypeRef.getType());
TypeRef<List<Integer>> integerTypeRef = new TypeRef<>() {};
System.out.println(integerTypeRef.getType());
f.put(stringTypeRef, List.of("a", "b", "c"));
f.put(integerTypeRef, List.of(1, 2, 3));
f.get(stringTypeRef).forEach(System.out::println);
f.get(integerTypeRef).forEach(System.out::println);
}
}
==
public abstract class TypeRef<T> {
private final Type type;
protected TypeRef() {
ParameterizedType superclass = (ParameterizedType) getClass().getGenericSuperclass();
type = superclass.getActualTypeArguments()[0];
}
@Override
public boolean equals(Object o) {
return o instanceof TypeRef && ((TypeRef)o).type.equals(type);
}
@Override
public int hashCode() {
return type.hashCode();
}
public Type getType() {
return type;
}
}
한정적 타입 토큰을 사용한다면, 이종 컨테이너에 사용할 수 있는 타입을 제한할 수 있다.
• AnnotatedElement.<T extends Annotation> T getAnnotation(Class<T> annotationClass);
asSubclass 메서드
• 메서드를 호출하는 Class 인스턴스를 인수로 명시한 클래스로 형변환 한다
// 코드 33-5 asSubclass를 사용해 한정적 타입 토큰을 안전하게 형변환한다. (204쪽)
// 원하는 어노테이션을 찾아준다.
public class PrintAnnotation {
static Annotation getAnnotation(AnnotatedElement element, String annotationTypeName) {
Class<?> annotationType = null; // 비한정적 타입 토큰
try {
annotationType = Class.forName(annotationTypeName);
} catch (Exception ex) {
throw new IllegalArgumentException(ex);
}
// Class<? extends Annotaion> aClass = annotationType.asSubclass(Annotaion.class);
**return element.getAnnotation(annotationType.asSubclass(Annotation.class))**;
}
// 명시한 클래스의 명시한 애너테이션을 출력하는 테스트 프로그램
public static void main(String[] args) throws Exception {
System.out.println(getAnnotation(MyService.class, FindMe.class.getName()));
}
}
===
@Retention(RetentionPolicy.RUNTIME)
public @interface FindMe {
}
@FindMe
public class MyService {
}