Kotlin Generics 공변성, 반공변성, 무변성

1,505 단어·4 분·원문(.md)

가변성? #

코틀린에서 가변성은 형식 매개변수가 클래스 계층에 영향을 주는 것을 말한다.

예를 들어서 형식 A의 값이 필요한 모든 클래스의 형식 B의 값을 넣어도 문제가 없다면 B는 A의 하위 형식이 된다.

val integer: Int = 1
val number: Number = integer // Number는 상위 자료형, Int는 하위 자료형

Int형으로 선언한 integer 변수가 Number형으로 선언한 number변수에 할당해도 아무 문제가 없는 것을 볼 수 있다.

이게 바로 가변성이다. 또다른 예로는 Int?, Int가 있다 Int?는 null을 포함하므로 Int?가 Int의 상위 자료형이 된다.

가변성의 3가지 유형 #

기본적으로 제네릭에서는 클래스간의 상위와 하위의 개념이 없어서 서로 무관하다.

만약 상위와 하위에 따른 형식을 주려면 어떻게 해야할까? 이때 변성이라는 새로운 개념이 적용된다.

가변성에는 공변성, 반공변성, 무변성 3가지 유형이 존재하는데 알아보도록 하자.

공변성 #

우선 Int형이 Number형의 하위 자료형인 것은 명확하다. 이 때 Class Box<out T>로 정의하면 Class Box<Int>Class Box<Number>의 하위 자료형이 된다.

그냥 그대로 이해하면 될 것이다. 공변성은 형식 매개변수의 상, 하위 개념을 그대로 가져오는 느낌이다.

A가 B의 하위 자료형이면 Class<\A>는 Class<\B>의 하위 자료형이다.

생산자 입장의 out 성질

반공변성 #

공변성과의 반대의 개념이다.

기본적으로 Int형이나 Number형의 하위 자료형이라는 사실은 명백하고, Class Box<in T>로 정의하면 Class Box<Number>Class Box<Int>의 하위 자료형이 된다.

A가 B의 하위 자료형이면 Class<\B>는 Class<\A>의 하위 자료형이 된다.

소비자 입장의 in 성질

무변성 #

무변성은 두 클래스 사이에서 아무런 관계또 없다는 개념이다.

Class<\A>, Class<\B>는 아무런 관계가 없다.

생산자 + 소비자

무변성 예시 #

제네릭 클래스를 인스턴스화 할 때 서로 다른 자료형을 인자로 사용하기 위해서는 자료형 사이의 상, 하위 관계를 잘 따져야한다.

형식 매개변수를 선언할 때 out, in 키워드 없이 그냥 선언하면 무변성으로 클래스가 선언된다.

Nothing, Int, Any 자료형의 관계를 살펴보면 Nothing <-> Int <-> Any의 상, 하위 관계를 가지고 있는데 이렇게 상하 관계가 명확해도 클래스가 무변성으로 선언되면 자료형 불일치 오류를 발생시킨다.

class Box<T>(val size: Int) // 무변성으로 제네릭 클래스 선언

fun main(){
    val anys: Box<Any> = Box<Int>(10) // 오류, 자료형 불일치
    val nothings: Box<Nothing> = Box<Int>(19) // 오류, 자료형 불일치
}

공변성 예시 #

형식 매개변수의 상, 하위 관계가 성립하고, 그 관계가 그대로 인스턴스 자료형으로 이어지는 경우 이를 공변성이라고 한다.

class Box<out T>(val size: Int) // 공변성으로 제네릭 클래스 선언

fun main() {
    val anys: Box<Any> = Box<Int>(10) // 객체 생성 가능!
    val nothings: Box<Nothing> = Box<Int>(10) // 오류, 자료형 불일치
}

out 키워드를 사용해서 공변성으로 클래스를 선언했다.

Any의 경우 Int의 상위 클래스이므로 Int형으로 생성한 객체도 Any에 포함할 수 있다.

하지만 Nothing의 경우에는 Int의 하위 클래스이므로 Int형을 ㅗ생성한 객체를 포함할 수 없다. (일반적인 상속처럼 생각하면 되려나)

공변성에서 자료형 제한하기 #

제네릭을 사용할 때 자료형 타입을 제한할 수 있다고 얘기했었다. 공변성에서도 똑같이 사용할 수 있다.

open class Animal(val size: Int){
    fun feed() = println("냠냠냠냠")
}

class Cat(val jump: Int): Animal(50)
class Dog(val weight: Int): Animal(80)

class Box<out T: Animal>(val element: T){
    // out 키워드를 사용해 공변성으로 선언
    // 따라서 하위 자료형이 상위 자료형으로 변환하는 것은 괜찮다.
}

fun main() {
    val c1: Cat = Cat(10)
    val c2: Dog = Dog(20)

    var a1: Animal = c1 // 하위 자료형이니 상위 자료형으로 변환 가능

    val c3: Box<Cat> = Box<Animal>(10) // 상위 자료형이 하위 자료형으로의 변환은 불가능
    val c4: Box<Animal> = Box<Cat>(10) // 자료형을 Animal로 제한했으므로 Animal, Cat, Dog를 제외한 다른 자료형은 사용 불가능.
}
Back-End/kotlin/공변성.md