Super Type Token
슈퍼 타입 토큰은 Neal Gafter가 고안한 List<String>.class
라는 클래스 리터럴이 존재할 수 없다는 한계를 뛰어 넘는 방법이다. 슈퍼타입 토큰은 상속과 Reflection을 조합해 List<String>.class
같은 근본적으로 사용할 수 없었던 클래스 리터럴을 타입 토큰으로 사용하는 효과가 있다.
타입 정보의 소실 다음 코드를 보자.
1 2 3 4 5 6 7 8 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()); }
리플렉션을 통해서 타입 정보를 받아올 수 없다. eraser에 의해 런타임에 타입 파라미터 정보가 제거되기 때문이다.
다음 코드를 보자.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 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 type = (ParameterizedType) t; System.out.println(type.getActualTypeArguments()[0 ]); }
1 java.util.Map<java.util.List<?>, java.util.Set<java.lang.String>>
static class Sub extends Sup<String> {}
방식은 Sup 클래스를 확장하면서 Sup 클래스를 새로운 타입으로 정의(Map\, Set\>)하고 이를 타입 파라미터로 지정했다. 그러면 리플렉션을 통해 런타임에 접근할 수 있도록 바이트코드에 남아있다.
getGenericSuperClass()는 바로 위의 슈퍼 클래스 타입을 반환하며, 그 슈퍼클래스가 ParameterizedType이면 실제 타입 파라미터들을 반영한 타입을 반환 한다.
즉 sub의 바로 위 슈퍼 클래스가 List\이라는 파라미터를 사용하는 ParameterizedType이면 getGenericSuperClass()를 통해 List\ 정보가 포함된 타입을 반환한다.
이를 이용해 익명 클래스로 만들 수 있다. 다음 코드를 보자.
1 2 3 4 5 6 7 8 9 10 static class Sup <T> { T value; } public static void main (String[] args) throws Exception { Sup sup = new Sup <Map<List<?>, Set<String>>>() {}; Type tp = sup.getClass().getGenericSuperclass(); ParameterizedType pTp = (ParameterizedType) tp; System.out.println(type.getActualTypeArguments()[0 ]); }
1 java.util.Map<java.util.List<?>, java.util.Set<java.lang.String>>
이 아이디어를 통해 타입 파라미터 정보를 얻어 오는 클래스를 만들 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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 = new TypeReference <List<String>>() {}; System.out.println(typeReference.type); }
이제 이를 통해 TypeToken이 가진 한계를 극복할 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 public class SuperTypeToken { static class TypeSafeMap { Map<TypeReference<?>, Object> map = new HashMap <>(); <T> void put (TypeReference<T> tr, T value) { map.put(tr, value); } <T> T get (TypeReference<T> tr) { if (tr.type instanceof Class<?>) return ((Class<T>)tr.type).cast(map.get(tr)); else return ((Class<T>)((ParameterizedType)tr.type).getRawType()).cast(map.get(tr)); } } 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 (); } @Override public boolean equals (Object obj) { if (this == obj) return true ; if (obj == null || getClass().getSuperclass() != obj.getClass().getSuperclass()) return false ; TypeReference<?> that = (TypeReference<?>)obj; return type.equals(that.type); } @Override public int hashCode () { return Objects.hashCode(type); } } public static void main (String[] args) throws Exception { TypeSafeMap m = new TypeSafeMap (); m.put(new TypeReference <String>(){}, "String" ); m.put(new TypeReference <Integer>(){}, 1 ); m.put(new TypeReference <List<String>>(){}, Arrays.asList("a" , "b" , "c" )); m.put(new TypeReference <List<Integer>>(){}, Arrays.asList(1 , 2 , 3 )); System.out.println(m.get(new TypeReference <String>(){})); System.out.println(m.get(new TypeReference <Integer>(){})); System.out.println(m.get(new TypeReference <List<String>>(){})); System.out.println(m.get(new TypeReference <List<Integer>>(){})); } }
1 2 3 4 String 1 [a, b, c] [1, 2, 3]
전체 코드는 다음과 같다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 public class SuperTypeToken { static class Sup <T> { T value; } static class Sub extends Sup <Map<List<?>, Set<String>>> { } static class TypeSafeMap { Map<TypeReference<?>, Object> map = new HashMap <>(); <T> void put (TypeReference<T> tr, T value) { map.put(tr, value); } <T> T get (TypeReference<T> tr) { if (tr.type instanceof Class<?>) return ((Class<T>)tr.type).cast(map.get(tr)); else return ((Class<T>)((ParameterizedType)tr.type).getRawType()).cast(map.get(tr)); } } 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 (); } @Override public boolean equals (Object obj) { if (this == obj) return true ; if (obj == null || getClass().getSuperclass() != obj.getClass().getSuperclass()) return false ; TypeReference<?> that = (TypeReference<?>)obj; return type.equals(that.type); } @Override public int hashCode () { return Objects.hashCode(type); } } public static void main (String[] args) throws Exception { Sup<String> s = new Sup <>(); System.out.println(s.getClass().getDeclaredField("value" ).getType()); Sub b = new Sub (); Type t = b.getClass().getGenericSuperclass(); ParameterizedType type = (ParameterizedType) t; System.out.println(type.getActualTypeArguments()[0 ]); Sup sup = new Sup <Map<List<?>, Set<String>>>() {}; Type tp = sup.getClass().getGenericSuperclass(); ParameterizedType pTp = (ParameterizedType) tp; System.out.println(type.getActualTypeArguments()[0 ]); TypeReference typeReference = new TypeReference <List<String>>() {}; System.out.println(typeReference.type); TypeSafeMap m = new TypeSafeMap (); m.put(new TypeReference <String>(){}, "String" ); m.put(new TypeReference <Integer>(){}, 1 ); m.put(new TypeReference <List<String>>(){}, Arrays.asList("a" , "b" , "c" )); m.put(new TypeReference <List<Integer>>(){}, Arrays.asList(1 , 2 , 3 )); System.out.println(m.get(new TypeReference <String>(){})); System.out.println(m.get(new TypeReference <Integer>(){})); System.out.println(m.get(new TypeReference <List<String>>(){})); System.out.println(m.get(new TypeReference <List<Integer>>(){})); } }
Reference
Neal Gafter’s blog 토비님 유투브 양봉수님 블로그 HomoEfficio님 블로그