개발

java generic, 슈퍼 타입 토큰

ka0oll 2020. 4. 16. 20:58

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 에서 사용한다.