class Cup<T>
T
는 variance 한정자(out
또는 in
)이 없기에 invariant(불공변성)이다.
Cup<Int>
와 Cup<Number>
, Cup<Any>
는 서로 관련이 없다.out
한정자가 필요하다.
A
가 B
의 서브타입일 때 Cup<A>
가 Cup<B>
이 서브타입이 된다.open class Dog
class Puppy: Dog()
class Cup<out T>
val b: Cup<Dog> = Cup<Puppy> // OK
val a: Cup<Puppy> = Cup<Dog>() // error
in
한정자는 반대 의미로 constravariant(반변성)으로 만든다.
A
가 B
의 서브타입일 때 Cup<A>
가 Cup<B>
의 슈퍼타입이 된다.class Cup<in T>
val b: Cup<Dog> = Cup<Puppy> // error
val a: Cup<Puppy> = Cup<Dog>() // ok
Int
→ Number
→ Any
타입 순으로 더 상위 타입인 걸 염두하고 (Int) → Any
타입의 함수를 생각해보자.fun printProcessedNumber(transaction: (Int) -> Any) {
print(transaction(42))
}
val intToDouble: (Int) -> Number = { it.toDouble() }
val numberAsText: (Number) -> Any = { it.toShort() }
val identity: (Number) -> Number = { it }
printProcessedNumber(intToDouble)
printProcessedNumber(numberAsText)
printProcessedNumber(identity)
Integer[] numbers = {1, 4, 2, 1};
Object[] objects = numbers; // convariant이기에 서브타입 취급하여 할당 가능
objects[2] = "B"; // 런타임 오류: ArrayStoreException
Array
를 invariant로 만들었다.
Array<Int>
를 Array<Any>
등으로 바꿀 수 없다.out
) 타입 파라미터와 public in
위치out
한정자가 붙은 타입 파라미터를 in
위치(함수 파라미터 등)에 쓸 수 있다면, 업캐스팅을 통해 타입 안정성이 깨질 수 있기 때문에 에러가 발생한다.class Box<out T> {
private var value: T? = null
fun set(value: T) { // 코틀린에서 컴파일 오류 발생!
this.value = value
}
}
val dogHouse = Box<Dog>()
val box: Box<Any> = dogHouse // Puppy는 Any의 서브 타입이기에 할당 가능 (convariant)
box.set("Some string") // ?
box.set(42) // ?
Int
나 String
을 설정하려고 하지만 해당 위치는 Dog
만을 위한 자리다.private
으로 제한하면 오류가 발생하지 않는다.
class Box<out T> {
private var value: T? = null
private fun set(value: T) {
this.value = value
}
}
out
한정자)는 public out
한정자 위치에선 안전하기에 제한되지 않는다.List<T>
List<T>
는 interface List<out T>
로 정의되어 있다.out T
는 T
가 반환 위치(out
position)에만 쓰인다는 의미List<Int>
를 List<Number>
로 안전하게 업캐스팅할 수 있다.
val numbers: List<Number> = listOf(1, 2, 3)
MutableList<T>
MutableList<T>
는 interface MutableList<T>
로 정의되어 있고, out
또는 in
한정자가 없다.T
가 입력 위치(in
position, 예: add
/set
메서드의 파라미터)와 반환 위치(out
position, 예: get
메서드의 반환값) 모두에 사용된다.MutableList<Int>
를 MutableList<Number>
로 업캐스팅하면, MutableList<Number>
에서 Float
값을 추가할 수 있게 되어 타입 안정성이 깨진다.MutableList<T>
는 invariant으로 설계되어, 타입이 정확히 일치할 때만 대입이 가능하다.in
) 타입 파라미터와 public out
위치in
한정자를 메서드 리턴 타입(out
위치)에도 쓸 수 없다.
out
위치)에 in
한정자 타입 파라미터를 사용하면, 실제로 어떤 타입이 반환될지 보장할 수 없으므로 타입 안정성이 깨진다.class Box<in T> {
val value: T // 에러
}
val noSpot: Box<Nothing> = Box<Car>(Car())
val boat: Nothing = noSpot.value // Car를 위한 공간이다
private
일 땐 문제가 되지 않는다.class Box<in T> {
private val value: T
}
// 선언 쪽
class Box<out T>(val value: T)
val boxStr: Box<String> Box("Str")
val boxAny: Box<Any> boxStr
class Box<T>(val value: T)
val boxStr: Box<String> = Box("Str")
// 사용하는 쪽
val boxAny: Box<out Any> = boxStr
MutableList<out T>
라면 (원랜 불공변임) get
하면 T
타입이 나오겠지만 set
은 Nothing
타입 아규먼트도 전달 가능해지기에 사용할 수 없다.MutableList<in T>
라면 get
과 set
모두 사용 가능하지만 get
의 경우 Any?
타입이 반환되는데 이는 모든 타입의 슈퍼타입을 가진 리스트가 존재할 가능성이 있기 때문이다.코틀린은 타입 아규먼트 관계에 제약을 걸 수 있는 강력한 제네릭 타입을 제공한다.
out
위치에 사용 가능하다.in
위치에 사용 가능하다.코틀린에서는
List
와 Set
, Map
타입 파라미터는 convariantMutableList
, MutableSet
, MutableMap
타입 파라미터는 invariantout
한정자)를 사용in
한정자)를 사용