[Effective Kotlin] 아이템 24. 제네릭 타입과 variance 한정자를 활용하라

Jimin Lim·2023년 8월 12일
0

Effective Kotlin

목록 보기
24/39
post-thumbnail

아이템 24

제네릭 타입과 variance 한정자를 활용하라

제네릭

class Cup<T>

위 코드의 T는 variance 한정자 (out or in)이 없어, 기본적으로 invariant이다.

여기서 invariant란, 제네릭 타입으로 만들어지는 타입이 서로 관련성이 없다는 것을 의미한다.
관련성을 갖기 위해서는 out, in과 같은 variance 한정자를 붙여야 한다.

Out - covariant

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

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() }

파라미터 타입은 높은 타입으로 이동하고, 리턴 타입은 낮은 타입으로 이동한다.

variance 한정자의 안정성

자바 배열을 covariant이다. (상위 타입으로 받음)

Integer[] numbers = {1, 4, 2, 1};
Object[] objects = numbers;
objects[2] = "B"; // 런타임 오류

위와 같이 numbers를 Obejct로 캐스팅 해도 실질적인 타입이 바뀌는 것은 아니므로 오류가 발생한다.
따라서 코틀린은 이 문제를 해결하기 위해 invariant 만들어, 업캐스팅하지 못하도록 한다.

또한 코틀린은 public in 한정자 위치에 covariant 타입 파라미터(out)가 오는 것을 막는다. -> 예시 이해가 어렵다...

variance 한정자의 위치

선언 부분

이 위치에 사용하면 클래스와 인터페이스 선언에 한정자가 적용된다. 즉, 클래스와 인터페이스가 사용되는 모든 곳에 영향을 준다.

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

정리

  • 타입 파라미터는 기본적으로 무공변이다.
  • out을 쓰면 공변으로 만들 수 있다.
  • in은 반공변으로 만들 수 있다.
profile
💻 ☕️ 🏝 🍑 🍹 🏊‍♀️

1개의 댓글

comment-user-thumbnail
2023년 8월 12일

즐겁게 읽었습니다. 유용한 정보 감사합니다.

답글 달기