(Kotlin/코틀린) 반공변성(in) 완전 정리 - 왜 Comparator 인가?

개요


1. 한줄 정의

in은 “이 타입은 데이터를 받아들이기만 한다(Consumer)”는 의미입니다.


2. 공변성(out) 복습

open class Animal
class Dog : Animal()

val dogs: List<Dog> = listOf(Dog())
val animals: List<Animal> = dogs // ✅ out 덕분에 가능

3. 반공변성(in)이란?

interface Consumer<in T> {
    fun consume(value: T)
}
val animalConsumer: Consumer<Animal> = object : Consumer<Animal> {
    override fun consume(value: Animal) {
        println("동물: ${value}")
    }
}

val dogConsumer: Consumer<Dog> = animalConsumer // ✅ 가능

4. 왜 이게 안전한가?

dogConsumer.consume(Dog()) // ✅ Dog을 전달 -> Animal을 받는 코드가 처리 가능
val dogOnlyConsumer: Consumer<Dog> = object : Consumer<Dog> {
    override fun consume(value: Dog) { /* Dog 전용 처리 */ }
}

val animalConsumer2: Consumer<Animal> = dogOnlyConsumer // ❌ 위험
animalConsumer2.consume(Animal()) // Animal을 Dog 전용 코드에 전달 -> 타입 깨짐

5. in의 핵심 규칙

✔ in 타입은 “파라미터로 사용 가능”

interface Consumer<in T> {
    fun consume(value: T) // ✅ T를 받을 수 있음
}

❌ in 타입은 “반환값으로 사용 불가”

interface Consumer<in T> {
    fun produce(): T // ❌ 컴파일 에러
}

6. 실제 사례: Comparator

Kotlin 표준 라이브러리의 Comparator를 보면:

public interface Comparator<in T> {
    public fun compare(a: T, b: T): Int
}
val animalComparator: Comparator<Animal> = Comparator { a, b ->
    a.name.compareTo(b.name)
}

val dogs: List<Dog> = listOf(Dog("Bori"), Dog("Navi"))
dogs.sortedWith(animalComparator) // ✅ 가능

7. out vs in 정리

키워드 역할 방향 허용 위치 예시
out Producer (생산) 공변 (하위 → 상위) 반환값만 가능 List<out T>
in Consumer (소비) 반공변 (상위 → 하위) 파라미터만 가능 Comparator<in T>

8. 타입 관계 방향 비교

// 공변성(out): Dog <: Animal 이면 List<Dog> <: List<Animal>
List<Dog> ──────────────▶ List<Animal>   (같은 방향)

// 반공변성(in): Dog <: Animal 이면 Consumer<Animal> <: Consumer<Dog>
Consumer<Animal> ────────▶ Consumer<Dog> (반대 방향)

9. use-site variance (사용 지점 변성)

클래스 선언이 아닌, 사용하는 지점에서도 in을 붙일 수 있습니다.

fun copy(src: Array<out Animal>, dest: Array<in Animal>) {
    for (i in src.indices) {
        dest[i] = src[i]
    }
}

10. 실무 요약


참고



Related Posts