java generic, 슈퍼 타입 토큰
1. Generics
자바 5에서 추가된 기능으로서 제네릭(Generic)은 클래스 내부에서 사용할 데이터 타입을 외부에서 지정하는 기법을 의미한다
static 메소드 제네릭
public static class Utils{
public static <T> int size(T[] anArray) {
return anArray.length;
}
}
클래스 제네릭
class MyClass<T> {
T t;
private MyClass(T t){
this.t = t;
}
T getT(){
return t;
}
}
2. type eraser
자바는 컴파일시에 제네릭 타입 정보들을 제거 한다.
런타임시에는 실제로 타입정보는 존재 하지 않고 강제 캐스팅 된다.
class MyClass {
Object t;
private MyClass(Object t){
this.t = t;
}
Object getT(){
return (String) t; //이렇게 강제 캐스팅된다.
}
Type getTypeName(){ //결과는 Object타입이다.
try {
return this.getClass().getDeclaredField("t").getType();
} catch (NoSuchFieldException e) {
e.printStackTrace();
return null;
}
}
}
typeSafetyMap 예제
public class Test {
public static void main(String[] arg) {
TypeSafetyMap map = new TypeSafetyMap();
map.put(Integer.class, 1);
map.put(Integer.class, "hello");
map.put(String.class, "hello");
map.put(String.class, 2);
map.put(List<String>.class, Arrays.as("A","B","C"));
map.put(List<Integer>.class, Arrays.as(1,2,3));
//List.class 여서 덮어 씌우게 된다.
}
}
class TypeSafetyMap {
Map<Class<?>, Object> map;
{
this.map = new HashMap<>();
}
public <T> void put(Class<T> clazz, T t){
map.put(clazz, t);
}
public <T> T get(Class<T> clazz){
return clazz.cast(map.get(clazz));
}
}
3.슈퍼타입토큰
먼저 타입 토큰에 대해 알아보자.
클래스 리터럴 : String.class, Integer.class 등을 말하며, String.class의 타입은 Class<String>, Integer.class의 타입은 Class<Integer>다.
타입 토큰 : 컴파일타임 타입 정보와 런타임 타입 정보를 알아내기 위해 메서드들이 주고받는 클래스 리터럴을 타입토큰이라한다.
슈퍼 타입토큰
위의 type eraser내용에서 보듯이 List<String>.class와 같은 클래스 리터럴은 존재 하지 않는다
이런 제네릭 정보가 지워지는 문제 때문에 Super type token 기법이 생겨났다.
Super type token은 수퍼(상위)타입을 토큰으로 사용하겠다는 의미이다.
무슨말인가?
제네릭 정보가 컴파일시 런타임시 다 지워지지만 제네릭 정보를 런타임시 가져올 방법이 존재한다. 제네릭 클래스를 정의한 후에 그 제네릭 클래스를 상속받으면 런타임시에는 제네릭 정보를 가져올 수 있다.
슈퍼 타입을 토큰으로 사용한다는 의미이다. 말이 어렵지만 예제를 통해 풀어보자
List<String>.class 리터럴의 토큰 타입 Class<List<String>> 을 어떻게 구할까
바로 Class의 메소드의 public Type getGenericSuperclass() 메소드를 통해 구할수 있다.
getGenericSuperclass() 이용 하여 바로 위의 슈퍼 클래스의 타입을 반환한다.
상위타입은 제네릭의 타입토큰 정보가 존재한다.
public abstract class TypeReference<T> {
private Type type;
protected TypeReference() {
Type superClassType = getClass().getGenericSuperclass();
if (!(superClassType instanceof ParameterizedType)) { // sanity check
throw new IllegalArgumentException("TypeReference는 항상 실제 타입 파라미터 정보와 함께 생성되어야 합니다.");
}
this.type = ((ParameterizedType)superClassType).getActualTypeArguments()[0];
}
public Type getType() {
return type;
}
}
public void main(String[] args){
TypeReference<List<String>> ref = new TypeReference<List<String>>(){};//상속받는다
//바로위의 상위 타입에 대한 제네릭 타입은 존재한다.
Type type = ref.getType(); // List<String>.class의 타입정보이다.
}
슈퍼(상위)타입의 제네릭 파라미터정보인 Type을 통해 제네릭 파라미터 클래스 정보를 가져온다.
=> 최종적으로 T타입에 대한 정보를 가져올수있다.
@SuppressWarnings("unchecked")
private Class<T> getGenericTypeClass() {
try {
String className = ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0].getTypeName();
Class<?> clazz = Class.forName(className);
return (Class<T>) clazz;
} catch (Exception e) {
throw new IllegalStateException("Class is not parametrized with generic type!!! Please use extends <> ");
}
}
Spring 에서는 ParameterizedTypeReference 클래스를 제공해 json serialize/deserialize 에서 사용한다.