클래스 리터럴(.class)로 List<Integer> 에 대한 클래스 오브젝트를 가져올 때 타입 파라미터 적용한거를 구분해서 가져올 수 없다. Class 타입에는 제네릭 타입 파라미터에 대한 정보가 없다. 이 문제는 스프링 restTemplate에서 Http response body 정보를 자바 객체로 컨버팅 할 때도 발생한다.
슈퍼타입토큰(Super Type Token)
Neal Gafter가 만든 기법이다.
publicclassSuperTypeToken {staticclassSup<T> {T value; }publicstaticvoidmain(String[] args) throwsException {Sup<String> s =newSup<>();System.out.println(s.getClass().getDeclaredField("value").getType()); // class java.lang.Object }}
리플렉션을 통해서도 타입 파라미터 정보를 얻어올 수 없다. eraser에 의해서 타입 파라미터 정보가 런타임시 사라져 버린다.
비교를 해보면 Sup<String> s = new Sup<>();는 클래스에 인스턴스를 만들면서 타입을 준거다. 이렇게 작성한 코드는 하위호환성 문제(제네릭이 없는 1.5 이하 버전) 때문에 타입 정보를 런타임에 삭제해버린다. static class Sub extends Sup<String> {} 이 방식은 새로운 타입을 정의하면서 슈퍼클래스를 제네릭 클래스로 하고 타입 파라미터를 지정했다. 그러면 리플렉션을 통해서 런타임에 접근할 수 있도록 바이트코드에 남아있다.
publicclassSuperTypeToken {staticclassSup<T> {T value; }publicstaticvoidmain(String[] args) throwsException {// 로컬 클래스// class Sub extends Sup<Map<List<?>, Set<String>>> {}// 익명 클래스// new Sup<Map<List<?>, Set<String>>>() {};// 위의 방식처럼 익명 클래스를 바로 이용하면 된다Sup b =newSup<Map<List<?>,Set<String>>>() {};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>>
}}
이를 이용해 로컬 클래스로 만들수도 있고 익명 클래스로 만들수도 있다. 이를 이용해서 타입 파라미터 정보를 얻어오는 클래스를 만들어보자.
publicclassSuperTypeToken {staticclassTypeReference<T> {Type type;publicTypeReference() {Type sType =getClass().getGenericSuperclass();if (sType instanceof ParameterizedType) {this.type= ((ParameterizedType)sType).getActualTypeArguments()[0]; } else {thrownewRuntimeException(); } } }publicstaticvoidmain(String[] args) throwsException {// ParameterizedType이 아니다. 런타임에 타입 정보가 안남아있기 때문에 타입 정보를 Object로 인식한다. 그래서 RuntimeException이 발생한다.// TypeReference t = new TypeReference<List<String>>();// TypeReference를 상속받은 익명 클래스를 이용하기 때문에 바디부분{}이 필요하다.TypeReference t =newTypeReference<List<String>>(){};System.out.println(t.type); // java.util.List<java.lang.String> }}
이제 저 아이디어를 이용해서 기존 TypeToken이 가졌던 한계(타입 파라미터 정보를 가져올 수 없었던)를 해결해보자.
TypeReference의 Type은 JVM이 고유하게 만들어서 관리한다. 제네릭 정보가 들어가 있으니까 클래스처럼 처음부터 클래스 로딩할 때 인스턴스를 만들어서 가지고 있지는 않는다. 그런데 새로운 제네릭 타입을 TypeReference에 넘겨서 그 정보를 찾으려고 시도를 하면 그때 이 인스턴스가 없으면 새로운 걸 하나 만들어 낸다고 한다. 따라서 key값으로 줘도 된다.
스프링 4.0부터 추가된 ResolvableType을 이용하면 다양한 타입에 대한 접근이 편리하다.