[Kotlin] 제네릭 in, out과 자바의 와일드카드

박상군·2024년 10월 29일
0

Kotlin

목록 보기
6/9
post-thumbnail

Zoom In, Zoom Out

코틀린에서 in과 out 키워드는 제네릭 타입 파라미터의 공변성(variance)을 제어하기 위해 사용된다.
이는 제네릭 클래스나 함수가 상위 타입과 하위 타입 간의 관계를 명확히 하여 타입 안전성을 보장하도록 도와준다.

우선 코틀린의 in, out을 각각 살펴보면

1. 코틀린의 제네릭

out 키워드(공변성, Covariance)

out 키워드는 주로 생산자(Producer) 역할의 제네릭 타입에 사용되며, 제네릭 타입이 반환값으로만 사용될 때 적합하다. out 키워드를 사용하면 제네릭 타입이 자신의 하위 타입으로 대체 가능해진다.

open class Language
class Kotlin: Language()
class Swift: Language()

class Developer<out T>(private val language: T) {
    fun getLanguage(): T = language
}

@Test
fun developerTest() {
	val kotlinDeveloper: Developer<Kotlin> = Developer(Kotlin())
    val developer: Developer<Language> = kotlinDeveloper 
    // out이 없다면 Type mismatch 발생
    println(developer.getLanguage())
}

위 코드에서 Developer<out T> out 키워드가 없다면 Type mismatch가 발생한다.

out: 꺼내와서(out 시킨다) 읽는다. -> Write은 불가능
부모 클래스에 자식 클래스를 사용가능하게 해준다.

in 키워드(반공변성, Contravariance)

in 키워드는 소비자(Consumer) 역할의 제네릭 타입에 사용되며, 제네릭 타입이 입력 파라미터로만 사용될 때 적합하다. in 키워드를 사용하면 제네릭 타입이 자신의 상위 타입으로 대체 가능해진다.

open class Language
class Kotlin: Language()
class Swift: Language()

class Developer<in T: Language> {
    fun printLanguage(language: T) {
        println("this language is $language")
    }
}

@Test
fun developerTest() {
	val developer: Developer<Language> = Developer()
    val kotlinDeveloper: Developer<Kotlin> = developer 
    // in이 없다면 Type mismatch 발생
    kotlinDeveloper.printLanguage(Kotlin())
}

in: 넣어준다(in 시킨다) 즉, Write 할 수 있다. -> Read는 불가능

서버 클래스에 슈퍼 클래스를 사용가능하게 해준다.

in과 out이 없는 경우 (무공변성, Invariance)

제네릭 타입 파라미터에 in 또는 out 키워드를 사용하지 않으면, 해당 제네릭 타입은 무공변성(invariance)을 가지며, 명시된 타입만 허용된다. 즉, 하위 타입도 상위 타입으로 대체할 수 없고, 반대도 불가능하다.

open class Language
class Kotlin: Language()
class Swift: Language()

class Developer<T>(private var value: T) {
    fun get(): T = value
    fun set(newValue: T) { value = newValue }
}

val kotlinDeveloper: Developer<Kotlin> = Developer(Kotlin())
val developer: Developer<Language> = kotlinDeveloper // Type mismatch. 발생

2. 자바의 와일드 카드와 비교

코틀린의 in과 out 키워드는 자바의 와일드카드(? extends T와 ? super T)와 개념적으로 유사하지만, 언어 차원에서 조금 다르게 작동한다. 코틀린의 in과 out은 제네릭 타입 선언에서 공변성(variance)을 지정하기 위한 키워드인 반면, 자바의 와일드카드는 타입 사용 시 변수를 선언하는 데 사용된다.

코틀린 out vs 자바 ? extends T

  • 코틀린 out: out은 코틀린의 제네릭 타입에서 공변성(Covariance)을 지정하는 키워드. 이는 해당 타입이 반환만 가능하고, 소비(입력)될 수 없다는 것을 의미한다.
  • 자바 ? extends T: 자바의 ? extends T 와일드카드는 제네릭 타입이 T의 하위 타입만 허용한다는 의미. 주로 읽기 전용으로 사용되며, 안전하게 T의 하위 타입을 반환할 수 있게 한다.

코틀린 in vs 자바 ? super T

  • 코틀린 in: in은 제네릭 타입이 반공변성(Contravariance)을 가지도록 만든다. 즉, 해당 타입은 소비자(Consumer) 역할을 하며, T의 상위 타입을 허용한다. 반공변성으로 지정된 타입은 입력용 파라미터로만 사용할 수 있다.
  • 자바 ? super T: 자바의 ? super T 와일드카드는 제네릭 타입이 T의 상위 타입만 허용하게 만든다. T 타입의 값을 안전하게 소비(입력)할 수 있다.

주요 차이점을 정리 하자면

코틀린

  • 위치: 제네릭 타입 선언 시
  • 타입 안정성: 컴파일러가 in과 out을 체크
  • 제네릭 타입 정의 시 공변성과 반공변성을 설정하여, 타입 사용 시 명시적인 와일드카드 없이도 타입 안정성을 유지한다.

자바

  • 위치: 변수나 메서드 파라미터 선언 시
  • 타입 안정성: 와일드카드 사용 시 안전하게 타입 캐스팅 가능
  • 타입 사용 시점에서 와일드카드를 사용하여, 특정한 제네릭 타입의 상위 또는 하위 타입을 안전하게 사용할 수 있도록 한다.

간단 요약

  • out: 공변성(Covariance)
    읽기 가능(Read O), 쓰기 불가능(Write X)
    부모 클래스에 자식 클래스를 사용 가능
  • in: 반공변성(Contravariance)
    읽기 불가능(Read X), 쓰기 가능(Write O)
    자식 클래스에 부모 클래스를 사용 가능

tmi로 Kotlin의 List를 보면 out 키워드로 구현되어 있는 것을 볼 수 있다.

Reference

medium

0개의 댓글