Hanbit the Developer

Kotlin Documentation | Generics: in, out, where 본문

Mobile/Kotlin

Kotlin Documentation | Generics: in, out, where

hanbikan 2023. 5. 23. 14:50

Kotlin Documentation 시리즈에 대해

Category: Concepts - Classes and objects

문서 링크: https://kotlinlang.org/docs/generics.html


Variance: in, out

배경: String은 Object의 하위 타입이지만, MutableList<String>은 MutableList<Object>의 서브클래스가 아니기 때문에 기존의 generic을 통한 객체지향 프로그래밍 설계가 힘듦

Example

List는 covariance(public interface List<out E>)이기 때문에 정상 작동함

open class Animal
class Cat : Animal()
class Dog : Animal()

fun myAnimals(animals: List<Animal>) {
    println(animals[0])
}

fun main() {
    val cats: List<Cat> = listOf(Cat(), Cat())
    myAnimals(cats)
}

반면, MutableList는 invariant(using normal generic)이기 때문에 컴파일 오류가 발생함

open class Animal
class Cat : Animal()
class Dog : Animal()

fun myAnimals(animals: MutableList<Animal>) {
    println(animals[0])
}

fun main() {
    val cats: MutableList<Cat> = mutableListOf(Cat(), Cat())
    myAnimals(cats)
}

Star-projections

type을 모르고 있으나 안전하게 사용하기 위해 사용된다. Any와 달리, 한 번 타입이 결정되면 다른 타입을 사용할 수 없다.

Function<*, String>

where

upper bound를 여러 개 거는 경우에 사용된다.

fun <T : Comparable<T>> sort(list: List<T>) {  ... }

위 함수에서 Comparable<T>는 T로 올 수 있는 타입의 upper bound를 정의한다. 이때 upper bound를 하나 더 걸고 싶다면 아래와 같이 작성한다:

fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String>
    where T : CharSequence,
          T : Comparable<T> {
    return list.filter { it > threshold }.map { it.toString() }
}

Type erasure: is, as

if (something is List<*>) {
    something.forEach { println(it) } // The items are typed as `Any?`
}
inline fun <reified A, reified B> Pair<*, *>.asPairOf(): Pair<A, B>? {
    if (first !is A || second !is B) return null
    return first as A to second as B
}

val somePair: Pair<Any?, Any?> = "items" to listOf(1, 2, 3)

val stringToSomething = somePair.asPairOf<String, Any>()
val stringToInt = somePair.asPairOf<String, Int>()
val stringToList = somePair.asPairOf<String, List<*>>()
val stringToStringList = somePair.asPairOf<String, List<String>>() // Compiles but breaks type safety!
// Expand the sample for more details

Underscore operator

다른 타입에 의해 타입을 추론할 수 있는 경우 언더스코어를 통해 자동으로 추론하게 만들 수 있다:

abstract class SomeClass<T> {
    abstract fun execute() : T
}

class SomeImplementation : SomeClass<String>() {
    override fun execute(): String = "Test"
}

object Runner {
    inline fun <reified S: SomeClass<T>, T> run() : T {
        return S::class.java.getDeclaredConstructor().newInstance().execute()
    }
}

fun main() {
    // T is inferred as String because SomeImplementation derives from SomeClass<String>
    val s = Runner.run<SomeImplementation, _>()
    assert(s == "Test")
}

References

https://readystory.tistory.com/201

https://hungseong.tistory.com/30

https://medium.com/mj-studio/코틀린-제네릭-in-out-3b809869610e