본문 바로가기

IT/kotlin언어

[코틀린] 코틀린 제너릭

안녕하세요 남갯입니다.


오늘은 저번에 정리한 변성에 대해 이해가 부족한것 같아 


코틀린 인 액션의 제너릭에 대해서 정리해보고 블로깅을 해보려고합니다,



제너릭스


타입 소거로 인해 실행시점에서 제너릭의 클래스의 인스턴스의 타입인자 정보가 들어가 있지 않다.

JAVA의 JVM 제너릭스는 타입소거를 이용해서 구현된다. 이는 실행시점에 제너릭 클래스의 인스턴스의 타입 인자 정보가 들어있지 않다.

컴파일러는 List<Int>와 List<String>을 서로 다른타입이라 인식하지만 실행시점에서의 둘은 완전히 같은 타입의 객체이다.

따라서 문자열로 이루어진 List<String>이 어떤 타입을 가진 리스트인지 실행시점에는 검사할 수 없음. 따라서 if(value is List<String>)을 실행시점에는 알 수 없음. List인지의 여부는 확실하게 알수있지만 타입을 알 수없음.



그럼 왜 타입소거를 하는지?

하지만 타입소거를 하는 이유는 저장해야하는 타입의 정보가 줄어들어 전반적으로 메모리 사용량이 줄어든다는 장점이 있기 때문이다.



스타프로젝션

컴파일 단계에서는 List<Int>와 List<String>을 확인 할 수 없기 때문에 if(value is List<*>)을 통해 List타입인지 확인 가능.. 파라미터가 두개 이상이면 *두 개를 이용한다. 



as로 하면 어떻게될까?

부모클래스는 같더라도 실행시점에는 여전히 타입인자를 알 수 없고타입캐스팅은 여전히 성공하므로 조심해야한다. 



실행시점에 지워짐으로서

코틀린의 제너릭타입의 인자정보는 실행시점에 지워지기 때문에 제너릭 함수가 호출되도 그 함수의 본문에서는 호출시 쓰인 타입을 모른다.

따라서 value is T 라는것은 에러가 난다. 

inline 키워드를 통해 T를 실체화하면 인라인 함수의 타입 인자를 알 수 있다. 

(*inline은 그 함수를 호출한 식을 모두 함수의 본문으로 바꾼다 람다의 경우에는 무명클래스와 객체가 생성되지 않아 성능이 더 좋아질 수 있다.) 

그래서 inline 키워드와 함께 reified 키워드로 지정하게되면 T의 타입을 실행시점에 검사할 수 있다.


안드로이드 startActivity 간단하게 만들기

inline fun <reified T : Activity> Context.startActivity() {

   val intent = Intent(this, T::class.java)

   startActivity(intent)

}

startActivity<DetailActivity>()




변성의 존재이유 (공변,반공변,무공변)

변성은 

val strins = mutableListOf("abc","bac") 를 

fun addAnswer(list : MutableList<Any>){

  list.add(42)

}

에 넘기면 실제로 에러가 발생하므로 코틀린 컴파일러는 변성이라는 특징을 통해 List<String>이 List<Any> 타입에 넘기면 안된다는 점을 보여준다.



클래스타입 하위타입 

Number <--- Int , 

Int <--- Int,  

String <-X- Int,

Int? <--- Int , 

Int <-X- Int?

String의 Any의 하위타입이고 List<Any>를 파라미터로 받는곧에 List<String>받아도 괜찮을지? 다시 쓴다면 List<String>은 List<Any>의 하위타입인지이다.  



자바에서는 모든것이 무공변

자바에서는 모든 클래스가 무공변이다. 하지만

코틀린에서는 A가 B의 하위타입이면 List<A>는 List<B>의 하위타입이다, MutableList는 쓰기와 읽기 둘다 가능하지만 List는 읽기전용이기 때문이다. 따라서 이를 공변적이라 말한다. 


공변적임을 표시하려면 타입 파라미터 앞에 out키워드를 붙인다. 예를들어

interface Producer<out T> {

   fun produce{} : T

}



공변적임을 표시하려면 타입 파라미터 앞에 out키워드를 붙인다. 예를들어

interface Producer<out T> {

   fun produce{} : T

}


공변성 표시를 통해 

class Herd<T : Animal>

fun feedAll(animals : Herd<Animal>){

     

}

class Cat : Animal()

fun takeCareOfCats(cats: Herd<Cat>){

   feedAll(cats) <- 컴파일 오류가 발생함

}

변성을 지정해주지 않게되면 컴파일 에러가 발생한다. 따라서 out 키워드를 통해

class Herd<out : Animal> <- 공변적인 클래스로 변경해서 동작시키면 정상적으로 동작한다.



모든클래스를 공변적으로 만들수 없고 만들면 안전하지 않은 클래스도 있다. 따라서 타입 파라미터를 공변적으로 지정하면 클래스 내부에서 그 파라미터를 사용하는 방법을 제한한다. 타입의 안정성을 보장하기 위해 공변적 파라미터는 항상 아웃위치에만 있어야한다. 즉 클래스는 T타입의 값을 생산할 수는 있지만 T타입 값을 소비할 수는 없다.


interfaceTransformer<T> {

  fun transform(t: T..인위치 ) : T ..아웃위치

}


따라서 

class Herd<out T  Animal>{

    operator fun get (i : Int) : T {} T를 반환타입에 이용한다.

}


out 키워드는 

공변성, T를 아웃위치에만 사용가능하다라는 의미이다.



반공변성

interface Comparator<in T>{

   fun compare(e1 : T , e2: T) Int {}

}

이 인터페이스의 메소드는 T 타입의 값을 소비하기만 한다.