변성 in Kotlin
참고 자료
『코틀린 인 액션』
by 드미트리 제메로프, 스베트라나 이사코바- 9 장 제네릭스
- kotlin documentation - genericss#Variance
0. 들어가며¶
이전 글 에서
- 변성의 세가지 종류와 적용 조건 (PECS)
- 자바에서 extends, super 키워드를 이용해 convariance, contravariance 를 적용 하는 법
을 알아보았습니다.
이제,
- 코틀린에서 변성을 다루기 위해 사용하는 두가지
variance annotation
인in
,out
변경자 - 코틀린의
declaration-site variance
와type-projection (call-site variance)
두가지에 대해서 알아보겠습니다!
1. Declaration-site variance¶
선언 지점 변성은 자바에는 존재하지 않던 코틀린의 새로운 개념입니다. 그 이름과 공식문서의 예제에서 그 모티프를 알 수 있습니다. 공식문서의 예제를 살펴보겠습니다.
interface Source<T> { T nextT(); }
java 에서 위 generic interface 를 살펴보자. 선언 지점 변성을 이해하기 위해 필요한 이 인터페이스의 중요한 특징은 타입 파라미터 T
를 메서드의 반환 값에서만 사용하고 메서드의 파라미터에는 포함되지 않는 다는 것이다.
당연히 위 코드는 컴파일러에러가 발생합니다.
하지만 지난 시간 정리한 PECS 와 함께 개념을 다시 생각해보면 Source<T>
인터페이스는 T
타입의 Producer 입니다. 또한 위 코드에서 실제로 String 을 생성 할지라도 사용하는 측 입장에서 Object 를 생성한다고 이해해도 아무 문제가 없습니다.
선언 지점 변성 declaration-site variance 은 이러한 모티프에서 도입되었다. 선언 지점 변성을 통해 컴파일러에게 Source<T>
는 T
타입을 생성만 하고 따라서 Source<T>
대신 Source<Object>
를 사용해도 문제가 없다는 것을 알려 줄 수 있다. kotlin 에서는 두가지 variance annotation
- in
, out
변경자를 통해 이를 나타낼 수 있습니다.
interface Source<out T> { fun nextT(): T }
fun demo(strs: Source<String>) {
val objects: Source<Any> = strs
// This is OK, since T is an out-parameter
// Source<String> 이 Source<Any> 의 하위 타입처럼 동작한다.
}
in 변경자는 반대로 타입 T 를 사용하기만 하는 경우 도입하여 반공변성을 추가 할 수 있습니다.
interface Comparable<in T> { operator fun compareTo(other: T): Int }
fun demo(x: Comparable<Number>) {
x.compareTo(1.0)
// 1.0 has type Double, which is a subtype of Number // Thus, you can assign x to a variable of type Comparable<Double>
val y: Comparable<Double> = x // OK! - 반공변성
}
in
/out
변경자의 이름을 통해 그 역할을 쉽게 유추할 수 있습니다!
2. Use-site Variance: Type Projections¶
클래스/ 인터페이스 전체가 타입 파라미터 T 에 대해서 in/out 한 성질을 가지는 경우 선언 지점 변성을 적용할 수 있습니다. 그렇지 않은 경우에는 Use-site varinace 를 적용 할 수 있습니다. Array<T>
는 그런 클래스 중 하나입니다.
class Array<T>(val size: Int) {
operator fun get(index: Int): T { ... } // T 를 생산 (out)
operator fun set(index: Int, value: T) { ... } // T 를 사용 (in)
}
Use-site Variance
는 그 이름 처럼 사용하는 시점에 T
를 produce 하기만 하는 메서드만 혹은 T
타입을 consume 하는 메서드만 사용 하는 경우라면 사용 시점에 변성을 정의해 주는 방식입니다.
배열 클래스에 use-site variance 를 적용한 두 예시 를 살펴보고 글을 마치겠습니다.
fun fill(dest: Array<in String>, value: String) { ... }
Created: February 18, 2023