개발일지

Kotlin in A..Z (26) - 상, 하위 형식의 가변성 본문

Kotlin (코틀린)

Kotlin in A..Z (26) - 상, 하위 형식의 가변성

강태종 2020. 7. 19. 04:37

가변성

  • 가변성이란 매개변수가 클래스에 영향을 주는 것을 말한다.
  • 예를 들어 Int클래스는 Number클래스를 상속받았고, Number클래스 변수에 Int의 인스턴스를 대입할 수 있다. 이때 Int는 Number의 하위 형식이 된다.

가변성의 3가지 유형

용어 의미
공변성(Covariance) A가 B의 하위 형식이면 C<A>는 C<B>의 하위 형식이다.
생산자 입장의 out 성질
반공변성(Contravariance) A가 B의 하위 형식이면 C<B>는 C<A>의 하위 형식이다.
소비자 입장의 in성질
무변성 C<A>와 C<B>는 아무 관계가 없다.
생산자 + 소비자

무변성

기본 값이다.

Any -> Number -> Int의 관계가 있지만 무변성이 적용되면 아무 관계가 없기 때문에 Type mismatce가 발생한다.

class Box<T>

fun main() {
    val any: Box<Any> = Box<Number>() // Error Type mismatch
    val int: Box<Int> = Box<Number>() // Error Type mismatch
}

공변성 (out)

Any -> Number관계이기 때문에 Any에 Number을 대입할 수 있다

Number -> Int관계에서는 Int에 Number을 대입할 수 없다.

// 공변성 정의
class Box<out T>

fun main() {
    val any: Box<Any> = Box<Number>() // 공변성 Any -> Number이기 때문에 Any에  Number을 대입할 수 있음
    val int: Box<Int> = Box<Number>() // Error Type mismatch
}

반공변성 (in)

Any -> Number관계이기 때문에 Any에 Number을 대입할 수 없다

Number -> Int관계에서는 Int에 Number을 대입할 수 있다.

// 반공변성 정의
class Box<in T>

fun main() {
    val any: Box<Any> = Box<Number>() // Error Type mismatch
    val int: Box<Int> = Box<Number>() // 반공변성 Number -> Int 이기 때문에 Int에 Number을 대입할 수 있음
}

공변성에 따른 자료형 제한

  • 공변성(out)은 반환하는 함수만 가질 수 있다.
  • 반공변성(in)은 들어오는 함수만 가질 수 있다.
  • 공변성을 사용하면 형식 매개변수를 갖는 프로퍼티는 private으로 선언하거나 val로 선언해야 한다. (var로 설정하면 들어오는 함수인 setter가 자동으로 생성되기 때문, private으로 설정하면 setter가 안생기니까)
  • 반공병성, 공변성을 사용하면 형식 매개변수를 갖는 프로퍼티는 private으로 선언해야 한다. (val이나 var로 선언하면 getter가 자동으로 생성되기 때문, private으로 설정하면 getter가 생성되지 않는다.)

 

코드

class Box1<in T> (private var element: T, val h: T){
    // Error 반공변성은 반환하는 자료형을 쓸 수 없다.
    fun getElement(): T {
        return element
    }

    fun setElement(element: T) {
        this.element = element
    }
}

class Box2<out T> (private var element: T, val h: T){
    fun getElement(): T {
        return element
    }

    // Error 공변성은 들어오는 자료형에는 쓸 수 없다.
    fun setElement(element: T) {
        this.element = element
    }
}

자료형 프로젝션

- 선엄 지점 변성

클래스를 선언하면서 클래서 자체에 가변성을 지정하는 방식

클래스를 선언하면서 전체적으로 지정하기 때문에 클래스를 사용하는 장소에서는 따로 자료형을 지정할 필요가 없다.

 

코드

// 클래스 정의와 동시에 설정
class Box<out E>(private val element: E) {
    
}

- 사용 지점 변성

메서드의 매개변수, 제네릭 클래스를 생성할 때 가변성을 지정하는 방식

 

코드

class Box<E>(val list: ArrayList<E> = ArrayList()) {

}

fun <T> get(index: Int, box: Box<out T>): T {
    return box.list[index]
}

- 스타 프로젝션

  • Box<Any?>는 모든 자료형을 받을 수 있지만 Box<*>은 구체적으로 자료형이 결정되면 그 자료형과 하위 자료형만 받을 수 있다.
  • in으로 정의된 형식 매개변수를 *로 하면 in Nothing으로 취급한다.
  • out으로 정의된 형식 매개변수를 *로 하면 out Any?로 취급한다.

 

코드

class Box<in InE, out OutE> (inE: InE, outE: OutE) {
    // Error 반공변성으로 선언했기 때문(public으로 선언했기 때문에 getter가 생긴다 -> 공변성)
    val inE = inE
    val outE = outE

    fun function1(inE: InE) {
        println(inE)
    }

    // Error 공변성으로 선언했기 때문
    fun function2(outE: OutE) {

    }
}

// Box<Nothing, Any?> 와 같게 잡힌다
fun test(box: Box<*, *>) {
    // Error Nothing으로 취급하기 때문에
    box.function1(1)
    println(box.outE)
}
Comments