여기서 컴파일러 오류가 발생하는 이유는 useBag이 Bag<MyClass> 타입의 인자를 받는데, 실제로는 Bag<MyClassParent>를 넘기기 때문이다. 이 코드가 작동하게 만들려면 T에 대한 반공변성을 선언해야 한다. 하지만 Bag<T> 인터페이스에는 T가 out 위치에 있는 get(): T 함수가 있어서 T를 in으로 선언할 수 없다. 이런 경우에 사용 지점에서 타입을 제한하는 해법을 쓴다.
fun useBag(bag: Bag<in MyClass>): Boolean { // bag으로 작업 수행 return true }
이와 반대로 out 변성도 사용 지점에서 선언할 수 있다.
fun createBag(): Bag<out MyClassParent> = BagImpl2() class BagImpl2 : Bag<MyClass> { override fun use(t: MyClass): Boolean = true override fun get(): MyClass = MyClass() }
여기서 in MyClass와 out MyClassParent를 제약이 가해진 타입으로 생각할 수 있다. in MyClass는 in 위치에서만 쓰일 수 있는 MyClass의 하위 타입을 뜻하며, out MyClassParent는 out 위치에서만 쓰일 수 있는 MyClassParent의 하위 타입을 뜻한다. 컴파일러는 이런 제약을 검사한다. in MyClass와 out MyClassParent를 MyClass와 MyClassParent의 타입 프로젝션(type projection)이라고 부른다.
이 장을 시작할 때 언급했던 것처럼 이 장은 코틀린의 개요에 불과하다. 이제부터는 안전한 프로그램이라는 관점에서 코틀린의 다른 특징을 자세히 살펴보겠다.