오늘도 더 나은 코드를 작성하였습니까?

반공변성: 뒤집힌 하위 타입 관계 본문

Kotlin in Action/코틀린 답게 사용하기

반공변성: 뒤집힌 하위 타입 관계

hik14 2021. 3. 2. 19:16

반공변 클래스의 하위 타입 관계는 공변 클래스의 경우와 반대이다.

 

Comparator 인터페이스 살펴보기.

interface Comparator<in T>{
    fun compare(e1: T, e2: T): Int
}

이 인터페이스는 T타입의 값을 소비하기만 한다. 따라서 타입 파라미터 앞에 in을 붙혀야 한다.

 

물론 특정 타입에 Comparator을 구현하면, 그 타입의 하위 타입에 속하는 모든 값을 비교할 수 있다.

 

 

fun main() {

    val anyComparator = Comparator<Any> { e1, e2 ->
        e1.hashCode() - e2.hashCode()
    }

    val strings = listOf<String>("a", "b", "c")
    strings.sortedWith(anyComparator) // 문자열과 같은 구체적인 타입을 비교하는데 모든 객체를 비교하는 AnyComparator을 사용할 수있다.
}

sortedWih는  Comparator<String>을 요구하기 때문에 조금 더 일반적인 타입을 비교 가능한, Comparator을 넘겨주는 것은 안전하다

 

그 타입이나 조상 타입을 비교할 수 있는 Comparator을 사용할 수 있다. 

이는  Comparator<Any>가 Comparator<String>의 하위 타입이다.

Comparator<String>가 필요한 위치에 Comparator<Any>가 들어가도 되기 때문이다.

 

하지만 Any는 String의 하위 타입이다. 서로 다른 타입인자에 대해서 Comparator의 타입이 뒤바뀌게 된다.

 

타입 B가 타입 A의 하위 타입인 경우 Consumer<A>가 Consumer<B>하위 타입 관계가 성립되면 

Consumer<T>는 타입 인자 T에 있어 반공변이다.

in 키워드는 그 키워드가 붙은 타입이 메소드 안으로 전달되어 소비된다는 뜻이며,

타입 파라미터의 사용을 제한함으로써 특정 하위 타입관 관계에 도달할 수 있다.

 

 

클래스나 인터페이스가 특정 타입 파라미터에 대해서는 공변적이고 다른 타입 파라미터에 대해서는 반공변적일 수도 있다.

 

interface Function1<in P, out R>{
    operator fun invoke(p: P): R
}

(P) -> R 는 Function<P, R>을 좀 더 알아보기 쉽게 적은것 이다.

 

Function1 은

첫번째 타입 파라미터와는 하위타입이 반대인 반공변적이고

두번째 타입 파라미터와는 하위타입이 동일한 공변성을 가진다.

 

예를들면 

open class Animal(val birthDay: Int)

fun Animal.getIndex(): Int = birthDay

class Cat(val name: String): Animal(name.hashCode())

fun enumerateCats(f: (Cat) -> Number){
    println(f.invoke(Cat("나비")))
}

fun main() {
    enumerateCats(Animal::birthDay)
}

Animal은 Cat의 상위 타입이지만,  반공변적으로 in 위치이고

numbers는 int의 상위타입으로 공변적으로 out의 위치이다.