Menu
Coddy logo textTech

in과 out을 사용한 변성

Coddy Kotlin 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 57개 중 40번째.

List<String>은 List<Any>가 필요한 곳에 전달할 수 있습니다. List는 요소를 꺼내 주기만 하기 때문입니다. 하지만 MutableList<String>은 전달할 수 없습니다. 이를 MutableList<Any>로 보는 코드가 문자열 목록에 Int를 추가할 수 있기 때문입니다.

fun describeAll(items: List<Any>) = items.joinToString { it::class.simpleName ?: "?" }
fun addNumber(items: MutableList<Any>) { items.add(42) }

main 내부에서:

val words = listOf("a", "b")
println(describeAll(words))
val mixed = mutableListOf<Any>("x")
addNumber(mixed)
println(mixed)
// addNumber(mutableListOf("a"))   // error: MutableList<String>은 MutableList<Any>가 아닙니다

출력:

String, String
[x, 42]

out T는 제네릭 클래스를 공변으로 만듭니다. 즉, 반환 타입으로 T 값을 생성하기만 하고, 값으로 받지는 않습니다. 따라서 Producer<Cat>은 Producer<Animal>로 간주됩니다.

open class Animal(val name: String)
class Cat(name: String) : Animal(name)

class Producer<out T>(private val item: T) {
    fun get(): T = item
}

fun greet(p: Producer<Animal>) = "hello ${p.get().name}"

main 내부에서:

val cats: Producer<Cat> = Producer(Cat("Tom"))
println(greet(cats))

출력:

hello Tom

in T은 이를 반변(contravariant)으로 만듭니다. 즉, 매개변수로 T 값만 소비합니다. 어떤 Animal이든 처리할 수 있는 소비자는 Cat 소비자를 대신할 수 있습니다.

open class Animal(val name: String)
class Cat(name: String) : Animal(name)

interface Feeder<in T> {
    fun feed(animal: T): String
}
class AnimalFeeder : Feeder<Animal> {
    override fun feed(animal: Animal) = "fed ${animal.name}"
}

fun feedCats(cats: List<Cat>, feeder: Feeder<Cat>) = cats.map { feeder.feed(it) }

main 내부:

println(feedCats(listOf(Cat("Tom"), Cat("Kit")), AnimalFeeder()))

출력:

[fed Tom, fed Kit]

컴파일러는 방향을 강제합니다. out 타입 매개변수는 메서드 매개변수의 타입이 될 수 없고, in 타입 매개변수는 반환될 수 없습니다. 일반적인 원칙은 생산자는 out, 소비자는 in입니다. 표준 라이브러리도 동일합니다: List<out E>, Comparator<in T>. 두 키워드 중 어느 것도 사용하지 않으면 타입 매개변수는 불변이며, 읽기와 쓰기를 모두 수행하는 MutableList<E>가 그 예입니다:

class Producer<out T>(private val item: T) {
    fun get(): T = item                  // 괜찮음: T가 나감
    // fun set(x: T) { }                 // 오류: T가 out으로 선언됨
}
interface Feeder<in T> {
    fun feed(animal: T): String          // 괜찮음: T가 들어옴
    // fun last(): T                     // 오류: T가 in으로 선언됨
}

분산은 타입이 사용되는 위치에 작성할 수도 있습니다. MutableList<in Int>는 Int 값을 받을 수 있는 모든 변경 가능한 리스트를 허용하며, 예를 들어 MutableList<Number>가 이에 해당합니다. 또한 MutableList<out Number>는 읽기만 허용합니다:

fun fillWith(target: MutableList<in Int>, n: Int) {
    for (i in 1..n) target.add(i)
}
fun total(source: MutableList<out Number>) = source.sumOf { it.toDouble() }

main 내부에서:

val numbers = mutableListOf<Number>(0.5)
fillWith(numbers, 3)
println(numbers)
println(total(mutableListOf(1, 2, 3)))

출력:

[0.5, 1, 2, 3]
6.0
challenge icon

챌린지

쉬움

두 인터페이스가 항목을 이동시킵니다. Source<out T>는 next()를 사용해 항목을 생성하며(비어 있으면 null을 반환), Sink<in T>는 accept(item)을 사용해 항목을 소비합니다. 이 인터페이스들은 제공됩니다. 목록의 항목을 하나씩 반환한 다음 null을 반환하는 ListSource<T>(items), 허용된 항목을 읽기 전용 items 목록에 보관하는 Collector<T>, 그리고 허용된 모든 항목을 세는 Sink<Any>인 Counter를 작성하세요. 그런 다음 소스의 모든 항목을 모든 싱크로 이동하고 이동한 항목 수를 반환하는 pipe(from, vararg to)를 작성하세요.

제공된 코드는 단어가 있는 한 줄을 Collector<String>으로, 숫자가 있는 한 줄을 Collector<Number>로 전달하며, 두 경우 모두 하나의 Counter에도 전달합니다. 이는 out과 in 덕분에 작동합니다. 즉, ListSource<Int>는 Source<Number>이고 Sink<Any>는 Sink<String>입니다. 이 코드는 개수와 두 컬렉션, counted: 7, after the end: null을 출력합니다.

여러분의 코드는 ListSource.kt, Collector.kt, Counter.kt, Pipe.kt, Source.kt, Sink.kt에 작성합니다. Main.kt에는 제공된 입력/출력 코드가 포함되어 있으며 편집할 수 없습니다.

직접 해보기

fun main() {
    // 제공된 입력/출력 코드: 그대로 유지하세요
    val input = generateSequence(::readLine).toList()
    val words = ListSource(input[0].split(" "))
    val ints = ListSource(input[1].split(" ").map { it.toInt() })
    val counter = Counter()
    val allWords = Collector<String>()
    val numbers = Collector<Number>()
    println("words moved: " + pipe(words, allWords, counter))
    println("numbers moved: " + pipe(ints, numbers, counter))
    println(allWords.items)
    println(numbers.items)
    println("counted: ${counter.count}")
    println("after the end: ${words.next()}")
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨

직접 연습해 보세요: Kotlin 플레이그라운드