[Java] Super Type Token

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());
}
1
class java.lang.Object

리플렉션을 통해서 타입 정보를 받아올 수 없다. 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님 블로그

Author: Song Hayoung
Link: https://songhayoung.github.io/2020/08/12/Languages/Java/supertypetoken/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.