Type Parameter

Generic

  • 제네릭은 선언 해두고 사용할 떄 타입 파라미터를 대신할 타입을 지정해서 사용한다.
  • 떄로는 java에서도 그렇지만 문맥상 추론이 가능하면 생략하기도 한다. (java <>)

바운드, 제약 (java의 super, extend)

  • 전체적인 개념이 java와 유사하다.
  • java의 T extends AbstractClassExample는 kotlin의 T: AbstractClassExample
  • java의 super는 kotlin에서 직접적으로 지원하지 않는다.

타입 소거

  • kotlin도 java와 같이 JVM 언어라 타입 소거가 벌어진다.
  • 런타임에 제네릭 코드는 parameter type의 차이를 인식할 수 없다.

구체화

  • java에서 와일드카드(?) 사용 시 제네릭 타입으로 캐스트하는 것은 허용되나
    1. 캐스트로 덮거나 : 컴파일 이후에 에러가 날 수 있다.
    2. reflection으로 타입 소거를 우회하거나 : 리플렉션으로 처음에 느리다.
  • 단점이 있다. 그래서 kotlin에서는 파라미터를 구체화하기 위해서 reified, inline을 사용해야 한다.
    inline fun <reified T> isA(value: Any) = value is T
    

reified: more concrete or real

변성

사전지식

(이전 자바 공부할 때 적었던 부분을 인용해보자. java/17.Generic.md)

자바의 공변성/ 반공변성

제네릭의 와일드카드를 배우기 앞서 선수 지식으로 알고 넘어가야할 개념이 있다. 조금 난이도 있는 프로그래밍 부분을 학습 하다보면 한번쯤은 들어볼수 있는 공변성(Covariance) / 반공변성(Contravariance) 합쳐서 ‘변성(Variance)’ 이라하는 개념이다. 변성은 타입의 상속 계층 관계에서 서로 다른 타입 간에 어떤 관계가 있는지를 나타태는 지표이다. 그리고 공변성은 서로 다른 타입간에 함께 변할수 있다는 특징을 말한다. 이를 객체 지향 개념으로 표현하자면 Liskov 치환 원칙[1]에 해당된다.

  • 공변 : S 가 T 의 하위 타입이면,

    S[] 는 T[] 의 하위 타입이다. List<S>List<T> 의 하위 타입이다.

  • 반공변 : S 가 T의 하위 타입이면,

T[] 는 S[] 의 하위 타입이다. (공변의 반대) List<T>List<S> 의 하위 타입이다. (공변의 반대)

  • 무공변 / 불공변 : S 와 T 는 서로 관계가 없다.

    List<S>List<T> 는 서로 다른 타입이다

제네릭은 공변성이 없다

객체 타입은 상하 관계가 있다 그러나 제네릭 타입은 상하관계가 없다. 즉, 제네릭의 타입 파라미터(꺾쇠 괄호) 끼리는 타입이 아무리 상속 관계에 놓인다 한들 캐스팅이 불가능하다. 왜냐하면 제네릭은 무공변 이기 때문이다. 제네릭은 전달받은 딱 그 타입으로만 서로 캐스팅이 가능하다.


[1] : 리스코프 치환 원칙은 1988년 바바라 리스코프(Barbara Liskov)가 올바른 상속 관계의 특징을 정의하기 위해 발표한 것으로, 서브 타입은 언제나 기반 타입으로 교체할 수 있어야 한다는 것을 뜻한다.

  • kotlin에서도 기본적으로 무공변(invariant)이다.
  • 불변 컬렉션 같은 타입은 타입 인자 사이의 하위 타입 관계가 그대로 제네릭 타입에도 유지된다.
  • 이런 타입 간의 하위, 상위 관계가 유지되는 경우를 알기 위해서 검토할 만한 경우의 수를 나열하면 아래와 같다.
    1. T 타입의 값을 반환하는 연산만 제공하고, T 타입의 값을 입력으로 받는 연산을 제공하지 않는 경우: readOnly : 불변컬렉션 -> 공변이다.
      • 기초적인 계약인 컬렉션에 넣은 값을 돌려준다는데 있다. 해당 타입은 Any에 대한 능력도 가진다.
      • 보통 불변 == 공변인 경우가 많으나 성립하지 않을 수도 있다. 반대로 가변 타입을 공변으로 만들 수도 있다. (가변 리스트에 삭제만 가능한 경우)
    2. T 타입의 값을 입력으로 받기만하고 결고 T 타입의 값을 반환하지 않는 제네릭 타입인 소비자: writeOnly: 반공변이다.
      • 단, 상위 타입이 하위에 대한 공변성은 있다.
    3. 둘 다 아닌 경우: 무공변이다.

선언 지점 변성

  • kotlin에서는 이런 변성에 대해서 선언할 수 있다.
    1. 기본으로 무공변
    2. 제네릭 타입 앞에 out 키워드를 붙여서 공변
    3. 제네릭 타입 앞에 in 키워드로 반공변

프로젝션을 사용한 사용 지점 변성

  • 위의 in/out을 붙이는 것을 프로젝션이라고 한다.
  • in -> 생산자.
  • out -> 소비자.