이 글은 기존 운영했던 WordPress 블로그인 PyxisPub: Development Life (pyxispub.uzuki.live) 에서 가져온 글 입니다. 모든 글을 가져오지는 않으며, 작성 시점과 현재 시점에는 차이가 많이 존재합니다.
작성 시점: 2017-10-19
Type Erasure 는 프로그램이 런타임에서 실행되기 전에 명시된 유형의 주석을 제거하는 프로세스를 뜻한다.
JVM에 익숙하다면 Generic의 Type Erasure 에 대해 알겠지만, 적어도 나는 익숙하지 않다.
두 개의 리스트가 있다고 해보자.
val listOfStrings = listOf("One", "Two", "Three")
val listOfNumbers = listOf(1, 2, 3)
그리고 메서드를 만들어 이 리스트 안에 있는 구성요소 들이 어떤 타입인지에 따라서 다르게 처리하고 싶다.
fun <T> printList(list: List<T>) {
when (list) {
is List<String> -> println("This is a list of Strings")
is List<Integer> -> println("This is a list of Integers")
}
}
컴파일을 해보면, Cannot check for instance of erased type: List<String>
라는 오류가 발생한다.
즉, List 에서 T에 들어오는 Generic는 런타임에서 실행되기 전 지워지기 때문에 판단할 수 없다는 뜻이다.
그 이유로는 예전 JVM에서 메모리를 절약하기 위해 그렇다고 한다.
여기서 리스트 가지고 할 수 있는 것은, 겨우 이 것 밖에 없다.
if (list is List<*>) {
println("This is a list");
}
만일 제너릭을 활용한다면 이정도 까지만 가능하다.
fun <T> printList(obj: T) {
when (obj) {
is Int -> println("This is an int")
is String -> println("This is an Strings")
}
}
이 정도 까지만 가능하지만, 이 것이 과연 우리가 원하는 것일까? 하면 아니다.
코틀린에서는 기존 자바와 다르게 이 문제를 해결할 수 있는 방법이 있다.
바로 inline 과 reified의 조합이다.
fun <T> ereased(input: List<Any>) {
if (input is T) {
}
}
이와 같은 코드가 있다고 하면, 역시 T 부분에 Cannot check for instance of erased type: T
란 오류가 뜰 것이다.
T 앞에 reified 를 붙여주면, T 부분에는 오류가 없어졌지만 Only type parameters of inline functions can be reified
란 오류가 뜬다.
그래서 저 함수 자체를 inline 선언을 하면 드디어 코드를 쓸 수 있게 된다.
inline fun <reified T> ereased(input: List<Any>) {
if (input is T) {
}
}
이 것 말고도, 실제로 들어오는 코드의 실제 타입을 알 수도 있다.
inline fun <reified T> typeInfo() {
println(T::class)
}
부를 때는 typeInfo<String>()
면 되는데, 호출 결과로는 class kotlin.String 로 String 의 실제 타입이 나온다.
https://github.com/JetBrains/kotlin/blob/master/spec-docs/reified-type-parameters.md 문서를 번역했다.
정의: '런타임-사용 가능 타입' 는 다음과 같은 경우에만 허용된다.
예제
런타임-사용 가능 타입이 허용될 때
결과적으로 T가 reified 된 타입 매개변수이면 다음과 같은 구성이 허용됨.
x is T
, x !is T
x as T
, x as? T
javaClass<T>(), T::class
reified된 매개 변수의 제한
메모 inline 가능한 매개 변수를 선언하지 않고 선언된 reified 매개 변수를 갖는 inline 함수에 대해서는 Warning가 표시되지 않는다.
JVM를 위한 구현 메모
inline 함수에서 reified된 타입 매개변수 T의 발생은 실제 타입 인수로 대체된다. 실제 타입 인수가 primitive type 일 경우에는 wrapper가 reified된 바이트 코드 안에서 실행된다.
open class TypeLiteral<T> {
val type: Type
get() = (javaClass.getGenericSuperclass() as ParameterizedType).getActualTypeArguments()[0]
}
inline fun <reified T> typeLiteral(): TypeLiteral<T> = object : TypeLiteral<T>() {} // T는 실제 타입으로 대체된다.
typeLiteral<String>().type // returns 'class java.lang.String'
typeLiteral<Int>().type // returns 'class java.lang.Integer'
typeLiteral<Array<String>>().type // returns '[Ljava.lang.String;'
typeLiteral<List<*>>().type // returns 'java.util.List<?>'