Kotlin – Generics (4) : Type projections and Summary

목차

  1. Invariance Types
  2. Covariant and Contravariant, out and in
  3. Declaration-Site Variance & Use-Site variance
  4. Type projections and Summary => 이번글

Type projections and Summary

세번째 글에 이어서…

그러면 코틀린에서는 자바와 같은 Use-Site Variance를 사용하지 못하는가?
대답은 No 이다.

코틀린에서는 Type projection / Star projections 라는 것을 제공한다.

코틀린에서는 Use-Site Variance 개념도 제공한다. Use-Site Variance를 사용할 때는 인스턴스의 실제 타입을 알 수 없을때 쓴다.
물론, 그에 따라 타입에 대한 제한적인 유형을 제공하고, 이 것을 Type projection 라고 부른다.

쓸 때에는 이렇게 사용한다.

interface KtWriteOnlyWithUseSite<T>{
    fun addItem(item: KtWriteOnlyWithUseSite<in T>)
    fun addAll(list: List<KtWriteOnlyWithUseSite<in T>>)
}

Star projection 은 어떤 타입의 아류형을 판단하는 안전한 방법을 제공하는데, 쓸 때에는 이렇게 사용한다.

if (element is List<*>)

다만 이 Star projection은 자바의 raw types 와 매우 닮았지만, 위에서도 말했다싶이 안전하다.


 

이로서 4개 글에 거쳐서 제너릭 관련 처리에 대해 알아본 것 같다.
물론, 여기서 다루지 않은 것도 있다.

  • <T: Serializable> – 받을 수 있는 타입을 Serializable 를 구현하고 있는 객체만 받을 수 있게 제한함
  • where T: Comparable, T: Cloneable – 받을 수 있는 타입을 Comparable, Cloneable 둘 중 하나라도 구현하고 있는 객체만 받을 수 있게 제한함
  • 당연하게도, 메소드에도 제너릭을 가질 수 있음

코틀린으로 자주 코드 짜면서 어렴풋이 이해하고는 있었지만 누군가에게 한참 설명이 부족했던 부분에 대해 자세히 알 수 있어서 좋았다고 생각된다.

Kotlin – Generics (3) : Declaration-Site & Use-Site variance

목차

  1. Invariance Types
  2. Covariant and Contravariant, out and in
  3. Declaration-Site Variance & Use-Site variance => 이번글
  4. Type projections and Summary

Declaration-Site Variance & Use-Site variance

그러면 코틀린의 out와 자바의 ? extends T, 코틀린의 in과 자바의 ? super T가 어떻게 다를까.

같은 인터페이스를 두 언어로 작성해보자.
이 인터페이스는 아래와 같은 기능을 가질 것이다.

  • 아이템 하나 추가
  • 아이템 리스트 추가

코틀린으로는 아래와 같을 것이다.

interface KtWriteOnly<in T> {
    fun addItem(item: T)
    fun addAll(list: List<T>)
}

자바로는 아래와 같을 것이다.

import java.util.List;

public interface JavaWriteOnly<T> {
    void addItem(JavaWriteOnly<? extends T> item);
    void addAll(List<JavaWriteOnly<? extends T>> list);
}

차이점이 있다면, 코틀린에서는 T에 대한 반변 선언을 클래스 선언시에 했다는 점과, 자바는 T를 사용하려고 할 때 반변 선언을 한다는 것이다.
코틀린에서는 이 방식을 Declaration-Site Variance (선언 위치 변환) 이라 부르고, 자바는 이 방식을 Use-Site Variance (사용 위치 변환) 이라 부른다.

더 자세한 설명은 Declaration-site and use-site variance explained 글을 참고하면 된다.

이 두 개는 어떻게 보면 큰 차이점을 보이고 있는데,
코틀린에서는 한 번만 선언하면 그 클래스 안에서는 두번 다시 선언할 필요가 없다.
하지만 자바는 한 번에 선언하지 못하고 사용할 때 마다 계속 선언해야 한다.

그럼 코틀린에서는 자바와 같은 방식을 사용하지 못하는가?
대답은 No 이지만 다음 글 (아마 마지막이 될 것이다)에서 알아보려고 한다.

Kotlin – Generics (2) : Covariant and Contravariant, out and in

목차

  1. Invariance Types
  2. Covariant and Contravariant, out and in => 이번글
  3. Declaration-Site Variance & Use-Site variance
  4. Type projections and Summary

Covariant and Contravariant, out and in

코틀린의 List 는 Immutable interface 를 가지고 있다.

1번 글에서 있었던 operate 메소드에 Array<Person> 대신 List<Person>을 작성해보자.

fun operate(person: List<Person>)

신기하게도, Person 과 Employee 를 둘 다 받을 수 있다.
왜냐면, List는 어떤 클래스를 상속받는가에 따라 변할 수 있는 성질을 가지고 있기 때문이다.

이를 Covariant(공변) 라 부른다.

반대로 Employee 가 어떤 클래스를 상속하는가에 따라 변할 수 있는 성질을 가지고 있다면, 이를 Contravariant(반변) 라 부른다.

List는 제너릭 부분에 out 키워드를 가지고 있는데, 이 out가 이번 글에서 중요한 개념을 가지고 있다.

public interface List<out E> : Collection<E> {

예제 코드

제너릭 인터페이스를 작성해보자.

interface ReadOnlyRepo<T> {
    fun getId(id: Int): T
    fun getAll(): List<T>
}


그러면 Type parameter can have out variance 라고 힌트를 보여주는데, 이 인터페이스는 T 를 단일 객체이든, 리스트든 전달만하기 때문이다.

interface WriteOnlyRepo<T> {
    fun setId(id: T)
    fun setAll(list: List<T>)
}


반대로, 어떤 제너릭 메소드가 T를 쓰기만 한다면, IDE는 Type parameter can have in variance 라고 힌트를 보여준다.

정리

…조금 정리해보자.

클래스 C 와 T가 있다면, 클래스 C 가 T 파라미터에 따라 같이 변한다(공변)면, T 는 공변 파라미터다.
쉽게 생각해서 클래스 C가 T의 생산자가 될 수 있지만, 소비자가 되지 못한다.

반대로, 클래스 C가 T 파라미터에 따라 반대로 변한다(반변)면, T는 반변 파라미터다.
쉽게 생각해서 클래스 C가 T의 소비자가 될 수 있지만, 생성자가 되지 못한다.

자바로 표현하자면 공변은 ? extends T 이고, 반변은 ? super T 인 셈이다.

지난 글에서 조슈아가 제안했던 연성 기호인 PECS를, 코틀린 식에 맞게 재정의 해본다면
Consumer in, Producer out (CIPO) 가 되는 것이 아닐까(..)