Kotlin 中的泛型:如何不再害怕尖括号并编写干净代码

面向初学者的在线Python训练器

轻松学习Python,无需理论负担。在浏览器中直接解决带自动检查的实践任务,无需安装任何东西。

开始课程

引言:为什么泛型不可怕,反而很方便

\n\n

想象一下,你正在编写一个“盒子”类。里面可以放任何东西:书、苹果、手机。但如果你把它设计成针对 Object,那么每次取出时都不得不做 as? Book 转换,并祈祷不要遇到 ClassCastException。熟悉吗?

\n\n

这时 泛型(generics) 就派上用场了——它是 Kotlin 中一种强大的类型化机制,让你能够编写灵活的代码,同时又不牺牲类型安全性。在本文中,我们将由浅入深地讲解 Kotlin 中的泛型:基础类、函数、变型(in/out)、具体化类型和星投影。你将学会如何让代码更整洁,让编译器成为你最重要的盟友。

\n\n

本文为教育门户网站 pythonlib.ru 编写——它是你通往现代编程语言世界的向导。

\n\n

1. 基础:什么是 Kotlin 中的泛型?

\n\n

泛型(泛型类型) 允许将类、接口和函数参数化为在使用时指定的类型。这避免了代码重复,并在编译阶段提供了对类型的控制。

\n\n

来看一个最简单的例子:

\n\n
class Box<T>(val item: T)\n\nfun main() {\n    val bookBox = Box<String>("Kotlin in Action")\n    val intBox = Box<Int>(42)\n    // 编译器知道类型!\n    val title: String = bookBox.item\n}
\n\n

关键点:

\n
    \n
  • <T> — 类型参数(可以是任何字母,但按惯例使用 T、R、E、K、V)。
  • \n
  • 类型在创建对象时固定——运行时无需任何强制转换。
  • \n
  • 泛型仅在编译阶段工作(类型擦除),但 Kotlin 添加了“具体化”(稍后介绍)。
  • \n
\n\n

泛型函数

\n

Kotlin 中的类型化也适用于函数。例如,一个返回列表第一个元素的函数:

\n\n
fun <T> firstItem(list: List<T>): T? = list.firstOrNull()\n\nfun main() {\n    val numbers = listOf(1, 2, 3)\n    val first: Int? = firstItem(numbers) // T = Int\n    println(first) // 1\n}
\n\n

请注意:Kotlin 能够根据上下文推断类型——不必写成 firstItem<Int>(numbers)。

\n\n

2. 变型:in 和 out —— 如何不破坏类型安全

\n\n

对于初学者来说,最复杂的主题是 变型(variance)。它回答的问题是:List<String> 能否是 List<Any> 的子类型?

\n\n

在 Java 中,这通过通配符(? extends / ? super)解决。在 Kotlin 中,则是 out 和 in。

\n\n

out(协变)—— 只读

\n

标记 out 意味着泛型类型只能被“产出”(从方法中返回)。例如 List<out T>。

\n\n
interface Source<out T> {\n    fun next(): T  // 正确:T 在输出位置\n    // fun add(item: T) // 错误!T 在输入位置被禁止\n}\n\nfun demo(source: Source<String>) {\n    val anySource: Source<Any> = source // out 允许这样做\n}
\n\n

为什么这是安全的?如果我们只能从源中读取 String,那么可以将其视为 Any 的源。

\n\n

in(逆变)—— 只写

\n

in 则相反:类型只能出现在输入位置。经典例子是 Comparable。

\n\n
interface Comparable<in T> {\n    operator fun compareTo(other: T): Int\n}\n\nfun demo(comp: Comparable<Number>) {\n    val intComp: Comparable<Int> = comp // in 允许这样做\n}
\n\n

如果 Comparable<Number> 能够比较任何数字,那么它当然也能比较 Int。

\n\n

in out(不变)—— 默认情况

\n

如果既没有指定 in 也没有指定 out,那么类型是不变的(invariant)。这意味着 Box<String> 既不是 Box<Any> 的子类型也不是其父类型。这是最严格的模式,也是默认模式。

博客

书籍推荐