본문 바로가기

Kotlin

코틀린 제네릭스

728x90

JVM 의 제네릭스는 보통 타입 소거를 사용해 구현한다.

-> 타입 소거란 실행 시점(Run Time)에 타입 관련 정보가 소거되는 것을 의미한다.

-> 이는 JAVA에서 처음부터 제네릭스를 지원하지 않았기 때문이다. (참고로 제네릭스는 2003년 발표된 JDK 1.5 버전부터 포함된 내용이다.) 하위호환성을 중요시 하는 자바인 만큼 JDK 1.5 전버전과의 호환성때문에 이렇게 만들것으로 추측된다.

-> 런타임에 타입관련 정보가 없기 때문에 ClassCastException 등 타입으로 발생하는 비검사 예외가 발생할 여지가 있다.

코틀린에서는 실체화를 통해 타입 인자가 지워지지 않도록 하여 이러한 비검사 예외를 컴파일 시점에 체크할수 있도록 하는 장치가 있다.

var intList = ArrayList<Int>
var stringList = ArrayList<String>

println(intList::class)
//class java.util.Arrays$ArrayList (Kotlin reflection is not available)
println(stringList::class)) //
//class java.util.Arrays$ArrayList (Kotlin reflection is not available)

컴파일 시점에는 두 리스트를 다른 타입으로 인식하지만 , intList , stringList는 실행시점에는 오직 List 로만 볼수 있다.

실제 intList, stringList의 클래스를 조회해보면 타입정보가 안나오는걸 볼수 있다.

이러한 타입 소거의 한계로 is 검사를 할 수 없다.

     var lists= listOf("1","2","3");

    if(lists is List<String>){ //Cannot check for instance of erased type: List<Int>
        println("string works");
    }

    if(lists is List){ // true
        println("is raw list")
    }

 if(lists is List <Int>) // Cannot check for instance of erased type: List<Int> (타입이 소거됨)
    {
        println("is?????")
    }

실행시점에 List 라는 알 수 있지만 그 리스트가 어떤 타입의 리스트인지는 알 수가 없는 것이다. 이 때 스타 프로젝션(*)을 사용하면 된다.

*는 어떤 타입이 들어올지 미리 알 수 없어도 그 타입을 안전하게 사용하고 싶을 때 사용한다. 언제든지 모든 타입을 받을 수 있는 Any와 달리 한번 구체적인 타입이 정해지고 나면 해당 타입만 받을 수 있다. 정해지기 전에는 Any? 로 취급된다.

as, as? 에도 제네릭 타입을 사용할 수 있다. 실행시점에는 다른 타입으로 캐스팅해도 캐스팅에는 성공한다.(타입 정보가 없으므로) 그런 타입 캐스팅을 사용하면 "unchecked cast(비검사 캐스팅)이라는 경고가 뜬다.")

fun printSum(c: Collection<*>){
    val intList = c as? List<Int>?:throw IllegalArgumentException("List is expected");
    println(intList.sum())

}


fun main(){
    var intList = listOf(1,2,3)
    var stringList = listOf("1","2","3")
    var intSet  = setOf(1,2,3);

    printSum(intList); //정상
    printSum(stringList); //3번째 라인 sum에서 ClassCastException 발생
    printSum(intSet); //2번째라인 IllegalArguementException 발생

}

위 코드를 실행했을 때, Set은 List 가 아니란걸 알기 때문에 IllegalArguementException이 발생하는 한편

List에서 타입이 날아가버리므로 ClassCastException이 발생하는 걸 볼수 있다.

반면, 인라인 함수 안에서는 타입 인자를 알 수 있다. 함수에 inline 키워드를 붙이면 그 함수를 호출한 식을 모두 함수 본문으로 대체한다.

타입 파라미터에 reified 키워드를 지정하면 타입이 인스턴스 타입인지 검사 할수 있다.


inline fun <reified T>  isA(value : Any) = value is T

fun main(){

    println(isA<String>("abc")) // inline 함수이므로 코등상으로 println ("abc" is String) 으로 바뀐다 true
    println(isA<Int>("123")) // inline 함수이므로 코드상으로 println("123" is Int) 로 바뀐다. false

}

인라인 함수는 바이트코드가 그대로 삽입 되기 떄문에 타입 파라미터가 아니라 구체적인 타입을 사용한다. 그러므로 실햄시점에 타입 소거의 영향을 받지 않는다. 반면 자바에서는 코틀린 함수를 인라인이 아닌 일반함수로 호출하기 때문에 실제로 인라이닝이 되지 않기 때문에 위와 같이 사용 할수 없다.

이렇게 실체화된 타입 파라미터는 몇가지 제약이 있따.

다음과 같은 경우에 실체화된 타입 파라미터를 사용할 수 있다.타임 검사와 캐스팅(is, as)

  • 코틀린 리플렉션 API (::class)

  • 코틀린 타입에 대응하는 java.lang.cClass 얻기(::class.java)

  • 다른 함수를 호출할 때 타입 인자로 사용

반면 다음 경우에 실체화된 타입 파라미터를 사용할 수 없다.

  • 타입 파라미터의 인스턴스 생성

  • 타입 파라미터 클래스의 동반 객체 메소드 호출

  • 실체화된 타입 파라미터를 요구하는 함수를 호출하면서 실체화되지 않은 타입 파라미터로 받은 타입을 인자로 넘기기

  • 클래스, 프로퍼티, 인라인 함수가 아닌 함수의 타입 파라미터를 reified로 지정

'Kotlin' 카테고리의 다른 글

Kotlin 익명 클래스  (0) 2021.08.21
시스템  (0) 2021.06.13
by lazy vs lateinit  (0) 2021.03.10
JVM 언어 의 공변  (0) 2021.02.22