Menu
Coddy logo textTech

제네릭 클래스

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

클래스에도 타입 매개변수를 사용할 수 있습니다. Box<T>는 모든 타입의 값을 보유하며, 각 인스턴스가 타입을 결정합니다. Box<Int>는 Int 값만 허용합니다:

class Box<T>(var value: T) {
    fun describe() = "box with $value"
}

main 내부:

val numberBox = Box(42)
val textBox = Box("hello")
numberBox.value += 1
println(numberBox.describe())
println(textBox.describe())
// numberBox.value = "x"   // 오류: String은 Int가 아닙니다

출력:

box with 43
box with hello

컨테이너는 일반적인 제네릭 클래스입니다. 하나의 Stack<T> 구현으로 모든 요소 타입을 처리할 수 있으며, 컴파일러가 무엇이 들어가고 나오는지 확인합니다:

class Stack<T> {
    private val items = mutableListOf<T>()
    val size: Int
        get() = items.size

    fun push(item: T) { items.add(item) }
    fun pop(): T? = items.removeLastOrNull()
    fun peek(): T? = items.lastOrNull()
}

main 내부에서:

val stack = Stack<String>()
stack.push("a")
stack.push("b")
println(stack.pop())
println(stack.peek())
println(stack.size)

출력:

b
a
1

생성자 인수가 있는 경우에는 Box(42)처럼 생성자 인수에서 타입 인수가 추론됩니다. 그렇지 않으면 직접 작성합니다: Stack<String>(). 타입 인수가 서로 다른 인스턴스는 서로 다른 타입이므로 Stack<String>이 Stack<Int>와 혼동되는 일은 없습니다. 제네릭 클래스의 함수는 자체 타입 매개변수를 추가할 수도 있습니다:

class Box<T>(val value: T) {
    fun <R> map(f: (T) -> R): Box<R> = Box(f(value))
}

main 내부:

val length = Box("kotlin").map { it.length }
println(length.value + 1)

출력:

7

제네릭 인터페이스도 동일한 규칙을 따릅니다. 클래스는 제네릭 방식으로 구현하거나 하나의 특정 타입 인수에 대해 구현할 수 있습니다:

interface Repository<T> {
    fun add(item: T)
    fun all(): List<T>
}
class MemoryRepository<T> : Repository<T> {
    private val items = mutableListOf<T>()
    override fun add(item: T) { items.add(item) }
    override fun all() = items.toList()
}
class TagRepository : Repository<String> {
    private val tags = sortedSetOf<String>()
    override fun add(item: String) { tags.add(item.lowercase()) }
    override fun all() = tags.toList()
}

main 내부에서는 다음과 같습니다:

val ids = MemoryRepository<Int>()
ids.add(3); ids.add(1)
val tags = TagRepository()
tags.add("Kotlin"); tags.add("JVM")
println("${ids.all()} ${tags.all()}")

출력:

[3, 1] [jvm, kotlin]
challenge icon

챌린지

쉬움

History<T>(limit) 제네릭 클래스를 작성하여 마지막 limit개의 항목을 기억하도록 하세요. add(item)은 항목을 추가하고, 항목이 limit개보다 많아지면 가장 오래된 항목을 삭제합니다. latest는 가장 최근 항목 또는 null이며, all()은 가장 오래된 항목부터 가장 최근 항목까지를 읽기 전용 목록으로 반환합니다. 그런 다음 숫자 기록에만 존재하는 History<Int>.average() 확장 함수를 작성하여, 유지된 숫자들의 정수 평균을 반환하도록 하세요(비어 있으면 0).

제공된 코드는 limit, 단어가 있는 한 줄, 숫자가 있는 한 줄을 읽습니다. History<String>과 History<Int>를 채운 후 유지된 단어, 가장 최근 단어, 유지된 숫자, 그 평균 및 empty latest: null을 출력합니다.

코드는 History.kt에 작성합니다. Main.kt에는 제공된 입력/출력 코드가 있으며 편집할 수 없습니다.

직접 해보기

fun main() {
    // 제공된 입력/출력 코드: 그대로 유지하세요
    val input = generateSequence(::readLine).toList()
    val limit = input[0].toInt()
    val words = History<String>(limit)
    for (w in input[1].split(" ")) words.add(w)
    val numbers = History<Int>(limit)
    for (n in input[2].split(" ")) numbers.add(n.toInt())
    println("words: ${words.all()}")
    println("latest word: ${words.latest}")
    println("numbers: ${numbers.all()}")
    println("average: ${numbers.average()}")
    println("empty latest: ${History<Char>(3).latest}")
}
quiz icon실력 점검

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

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

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