제네릭 타입과 variance 한정자를 활용하라
class Cup<T>
위 코드의 T는 variance 한정자 (out or in)이 없어, 기본적으로 invariant이다.
여기서 invariant란, 제네릭 타입으로 만들어지는 타입이 서로 관련성이 없다는 것을 의미한다.
관련성을 갖기 위해서는 out, in과 같은 variance 한정자를 붙여야 한다.
class Cup<out T> //공변성으로 만듦
open class Dog
class Puppy: Dog()
fun main() {
//Puppy가 Dog의 서브 타입일때, Cup<Puppy>도 Cup<Dog>의 서브타입이다
val b: Cup<Dog> = Cup<Puppy>() // Puppy를 부모인 Dog로 받았음, 오류 없음
val a: Cup<Puppy> = Cup<Dog>() // 오류
val anys: Cup<Any> = Cup<Int>() // OK
val nothings: Cup<Nothing> = Cup<Int>() // 오류
}
In은 반대로 A가 B의 서브타입일 때, Cup<A>
가 Cup<B>
의 슈퍼타입이라는 것을 의미한다.
Puppy가 Dog의 서브타입일 때, Cup<Puppy>
가 Cup<Dog>
의 슈퍼타입
class Cup<in T>
open class Dog
class Puppy: Dog()
fun main() {
val b: Cup<Dog> = Cup<Puppy>() // 오류
val a: Cup<Puppy> = Cup<Dog>() // OK
val anys: Cup<Any> = Cup<Int>() // 오류
val nothings: Cup<Nothing> = Cup<Int>() // OK
}
코틀린 함수 타입의 모든 파라미터 타입은 contravariant이며, 리턴 타입은 covariant다. 함수 타입을 쓸 때는 자동으로 variance 한정자가 사용된다.
fun printProcessedNumber(transition: (Int) -> Any) {
print(transition(42))
}
val intToDouble: (Int) -> Number = { it.toDouble() }
val numberAsText: (Number) -> Any = { it.toShort() }
val identity: (Number) -> Number = { it.toInt() }
val numberHash: (Any) -> Number = { it.hashCode() }
파라미터 타입은 높은 타입으로 이동하고, 리턴 타입은 낮은 타입으로 이동한다.
자바 배열을 covariant이다. (상위 타입으로 받음)
Integer[] numbers = {1, 4, 2, 1};
Object[] objects = numbers;
objects[2] = "B"; // 런타임 오류
위와 같이 numbers를 Obejct로 캐스팅 해도 실질적인 타입이 바뀌는 것은 아니므로 오류가 발생한다.
따라서 코틀린은 이 문제를 해결하기 위해 invariant 만들어, 업캐스팅하지 못하도록 한다.
또한 코틀린은 public in 한정자 위치에 covariant 타입 파라미터(out)가 오는 것을 막는다. -> 예시 이해가 어렵다...
이 위치에 사용하면 클래스와 인터페이스 선언에 한정자가 적용된다. 즉, 클래스와 인터페이스가 사용되는 모든 곳에 영향을 준다.
class Box<out T>(val value: T)
val boxStr: Box<String> = Box("string")
val boxAny: Box<Any> = boxStr
이 위치에 variance 한정자를 사용하면 특정 변수에만 variance 한정자가 적용된다. 따라서 특정 인스턴스에만 적용할 때 이 코드를 작성한다.
class Box<T>(val value: T)
val boxStr: Box<String> = Box("string")
// 사용하는 쪽의 variance 한정자
val boxAny: Box<out Any> = boxStr
즐겁게 읽었습니다. 유용한 정보 감사합니다.