본문 바로가기
🔓 영구 노트

공변성, 무공변성, 반공변성 in kotlin

by 파랭이가 룰루랄라 2022. 10. 4.

kotlin in action 책의 9장 제너릭스의 변성 개념에 대해서 학습했다.

변성 개념은 List<String>와 List<Any>와 같이 기저 타입이 같고 타입 인자가 다른 여러 타입이 서로 어떤 관계가 있는지 설명하는 개념이다. - kotlin in action p.404-

제목에서 알 수 있지만 변성에는 공변성, 무공변성, 반공변성 3가지 유형의 개념이 존재한다. 아래 코드는 변성의 개념을 설명하기 위한 코드이다.

open class Animal

class Cat: Animal()

interface Feed<T> {
    fun feed()
}

Animal은 동물의 최상위 클래스, Cat은 동물의 하위 클래스, Feed는 동물의 행동을 정의한 인터페이스이다.

무공변성

fun animalFeed(element: Feed<Animal>) {
    element.feed()
}

무공변성은 위의 코드처럼 제너릭 클래스의 타입 파라미터에 아무것도 표시해주지 않은 것을 말한다. Feed<Animal>과 같이 표시를 하게 되면 animalFeed 함수의 element는 Feed<Animal>을 제외한 다른 모든 타입에 대해서 Type mismatch 에러를 발생시킨다.

따라서 무공변성은 다른 여러 타입에 대해서 허용하지 않고, 자기 자신의 타입만을 허용하는 것이다.

공변성

fun animalFeedOut(element: Feed<out Animal>) {
    element.feed()
}

공변성은 제네릭 클래스의 타입 파라미터에 out 키워드를 넣어준 상태를 말한다. 이렇게 되면 무공변성과는 다르게 자기 자신과 하위 타입에 대해서 접근을 허용하고, 상위 타입에 대해서는 Type mismatch 에러를 발생시킨다.

따라서 공변성은 자기 자신과 그 하위 타입을 허용하고, 상위 타입에 대해서는 허용하지 않는 것을 말한다.

반공변성

fun animalFeedIn(element: Feed<in Animal>) {
    element.feed()
}

반공변성은 제너릭 클래스의 타입 파라미터에 in 키워드를 넣어준 상태를 말한다. 반공변성이라는 말에 맞게 공변성과 반대로 작용하게 된다. 따라서 반공변성은 자기 자신과 그 상위 타입을 허용하고, 하위 타입에 대해서는 허용하지 않는 것을 말한다.

정리

Animal을 기준으로 보는 공변성, 반공변성, 무공변성

위의 그림에서 빨간색 원은 각 변성들이 허용하는 클래스들이다. 그림을 통해 각 변성에 대해서 허용하는 클래스와 허용하지 않는 클래스를 확인할 수 있다. 

검증

fun main() {
    val animal = object : Feed<Animal> {
        override fun feed() {
            println("animal feeding!")
        }
    }
    val cat = object : Feed<Cat> {
        override fun feed() {
            println("cat feeding!")
        }
    }
    val any = object : Feed<Any> {
        override fun feed() {
            println("any feeding!")
        }
    }
    val nothing = object : Feed<Nothing> {
        override fun feed() {
            println("nothing feeding!")
        }
    }

    // 무공변성
    // println("animalFeed(any) = ${animalFeed(any)}") Type mismatch
    println("animalFeed(animal) = ${animalFeed(animal)}")
    // println("animalFeed(cat) = ${animalFeed(cat)}") Type mismatch
    // println("animalFeed(nothing) = ${animalFeed(nothing)}") Type mismatch
    printLine()

    // 공변성
    // println("animalFeedOut(any) = ${animalFeedOut(any)}") Type mismatch
    println("animalFeedOut(animal) = ${animalFeedOut(animal)}")
    println("animalFeedOut(cat) = ${animalFeedOut(cat)}")
    println("animalFeedOut(nothing) = ${animalFeedOut(nothing)}")
    printLine()

    // 반공변성
    println("animalFeedIn(any) = ${animalFeedIn(any)}")
    println("animalFeedIn(animal) = ${animalFeedIn(animal)}")
    // println("animalFeedIn(cat) = ${animalFeedIn(cat)}") Type mismatch
    // println("animalFeedIn(nothing) = ${animalFeedIn(nothing)}") Type mismatch
}
  • 무공변성: 자기 자신을 제외한 제너릭 클래스에 대해서 Type mismatch를 발생시킨다.
  • 공변성: 자기 자신과 그 하위 타입에 대해서 정상적으로 작동하고, 상위 타입 Any에 대해 Type mismatch를 발생시킨다.
  • 반공변성: 자기 자신과 그 상위 타입에 대해서 정상적으로 작동하고, 하위 타입 Cat, Nothing에 대해 Type mismatch를 발생시킨다.

 

댓글