Menu
CoddyTech

泛型类

本课属于 Coddy Kotlin 学习旅程的“面向对象编程”单元。第 38 课,共 57 课。

类也可以有类型参数。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)。

提供的代码会读取限制数量、一行单词和一行数字。它会填充一个 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 在线编辑器