본문 바로가기

프로그래밍 언어 노트/JAVA | Kotlin

[JVM] Typereference 와 Type Erasure (타입 소거)

기본적으로 JAVA 를 포함한 JVM 기반 언어는 제네릭에있어서 불완전하다

"타입 소거" 가 되기 때문 (어느 번역서에서는 타입 지우개라고 적혀있던데.. 암튼)

이는 하위호환성을 유지하기 위하여 JAVA 가 택한 방법이고... 이로 인하여 JVM 기반언어는 타입 소거에서 자유로울수가 없다. 킹갓 닷넷은 다르다 킹갓 닷넷과는!

간단히 말하자면 타입안정성을 얻기 위하여 제네릭(제네릭에 내가 쓸 타입을 적어두었음)을 썻는데. 런타임에는 이 안정성이 보장되지 않는다는 소리 (내가 써둔 타입 정보가 사라짐)다.

따라서 JVM 상에서는 T든 U 든 써둔건 런타임에 죄다 Object 다.

//List<T> 제네릭 타입이 있다고 가정
List<Int> genericIntList = new List<Int>()				//이거나
List<Object> genericObjectList = new List<Object>()		//이거나 런타임에 똑같은 타입이다

 

 

런타임 타입 안정성이 보장되지 않아도 컴파일에서 이미 검사되는데 문제 없는거 아냐? 라고 생각할수있지만. 문제가 발생할수있다.

바로 리플렉션등과 같은 런타임 검사나, 외부 인터페이스간의 통신에서는 타입정보를 런타임에서 사용해야 하기 때문이다.

 

Spring 에서 HTTP 통신을위하여 기본적으로 젤 쓰기 쉬운게 RestTemplate 인데.
Resonpse 가 API 마다 바뀌므로 타입을 정해줄수있다.

앗 그런데 만약 Response 가 Json 이 아니라

// data class ID (val id) 같이 id 값은 가지는 클래스가 있다고 가정
[
	{"id":"hihi"},{"id":"hihi2"}
]

이런 리스트꼴이 Response 라면?

그렴 List<ID>가 Response 가 되는게 명백해 보인다.

근데 여기서 문제가 생길수밖에 없다.

컴파일타임에는 List<ID> 라는게 존재 하지만, 컴파일 이후에는 그런거 엄따.

List<Object> 가 되버린다.

 

인터페이스 통신은 런타임에 일어나므로, 저 Rest Template 을 이용해서 Response 를 받아보면

"그냥 List 형태의 무엇인가 라고 추측되는 결과물"  을 받을텐데. 이게 받을때는 심지어 아무 문제가 없다 (왜냐하면, 런타임에서 타입체크가 통과 됫기 때문에)

 

그러다가 이제 저걸 쓰려는 순간!

잉 JVM 런타임상에서는 걍 Object 라는거 밖에 모르는데 갑자기 이게 ID class 라고? 하면서 오류를 뿜어버린다. (이.. 이게 정적타이핑..?)

당연히 케스팅따위는 되지 않는다. 왜냐면 받은게 JSON/raw String 등과 같은 타입이지 JVM 메모리상에서 ID class 가 아니니까. 그냥 key-value 쌍인 LinkedHashMap 타입으로 변환되었고

Jackson 같은게 미들웨어로 뚝딱뚝딱 바꿔주어야 하는데, 이 런타임 상황에서 Jaskson 이 끼어들 여지가 없다. (Jackson 도 타입을 알아야 바꾸든 말든 할테니)

 

따라서 JVM 에서 제네릭 타입을 사용할때는 이런 상황을 항상 유의 해야한다.

 

그럼 해결책은 없느냐? 하면 또 아예 없는건 아니다.

간단하게 말하자면 그냥 타입정보를 어떻게든 또 넘겨줘서 쓸수있게 우회로를 마련해두어야 한다.

JAVA 에서의 슈퍼 타입 토큰 - Super 의 토큰을 넘겨줘서 어찌저찌 슉슉

Kotlin 의 Inline Reified - inline (컴파일 타임에 박히는 코드) 이므로 확인 가능

Scala 의 Type Tag - Type Tag 도 넘겨줘서 어찌저찌 슉슉

 

Spring 에서 쓰는 ParameterizedTypeReference가 슈퍼타입 토큰을 이미 이용한 예시라고 한다.

그래서 위 예시에서 ParameterizedTypeReference<List<ID>> 라고 받으면 잘 받아온다!

 

라고만 끝나면 문제가 발생할수있는데..

 

나름 좀더 쓰기 쉽게 하려고 추상화를 좀 해서. 나만의 Rest 용 Class (또는 함수)를 만드려고 했다고 쳐보자.

아무타입이나 받을수있어야 하므로.

fun <T> get(responseType: T
    ): T? {
        return template.exchange(
        	PATH, 
        	HttpMethod.GET, 
        	HttpEntity.EMPTY, 
        	ParameterizedTypeReference<T> // 이놈 타입은 ParameterizedTypeReference<T> 임에 유의
        ).body
    }

뭐 대충 이렇게 함수화 시킬수있는데.

여기서 responseType의 은 "ParameterizedTypeReference<T>" 이다.

저 T 는 제네릭이기 때문에 위 get<T> 함수를 호출하는쪽에서 List<ID> 를 넘겨주어도,

그래서 그냥 ParameterizedTypeReference<List<Object>> 가 된다. 따라서 코드에 명시적으로 

ParameterizedTypeReference<List<ID>> 박은게 아니라, 이걸 파라미터로 넘겨주는 역할이 되고, 그게 Generic 일때. 그냥 슈퍼토큰 동작 방식에 의하여 아까랑 똑같은 오류가 발생한다. 와우 어썸!

 

자바 - 일반 매개 변수와 일반 방법으로 스프링 RestTemplate를 사용하여 - 스택 오버플로 (stackoverflow.com)

 

Using Spring RestTemplate in generic method with generic parameter

To use generic types with Spring RestTemplate we need to use ParameterizedTypeReference (Unable to get a generic ResponseEntity<T> where T is a generic class "SomeClass<SomeGenericTyp...

stackoverflow.com

버그도 아니고 원래 이렇게 동작하는거라고 한다..

728x90