Kotlin 泛型介绍
泛型是 Kotlin 语言的关键特性之一,它让您能够编写灵活、类型安全且可复用的代码。如果您曾经写过 List<String> 或 Map<Int, User>,那么您已经在不知不觉中使用了泛型。但是,这些尖括号背后隐藏着什么,以及如何充分利用它们呢?在本文中,我们将探讨为什么 Kotlin 需要泛型,如何在实践中应用它们,以及哪些高级特性(型变、reified、where)能帮助您编写更简洁、更安全的代码。
本文面向已经熟悉 Kotlin 基础知识并希望加深理解的开发者。我们将涵盖从基本语法到生产代码中实际示例的所有内容。
为什么需要泛型?没有泛型的问题
假设您需要编写一个函数,该函数接受任意元素的列表并返回第一个元素。没有泛型,您会这样写:
fun getFirst(list: List<Any>): Any { return list[0]}现在,如果您传入一个 List<String>,您会得到一个 Any 类型的输出。要将其当作字符串使用,您必须进行显式类型转换:
val first = getFirst(listOf("Kotlin", "Java")) as Stringfirst.length // 危险!可能抛出 ClassCastException这既不方便也不安全。错误只会在运行时显现。泛型通过在编译时保留类型信息解决了这个问题。
泛型的主要优势:
- 类型安全 — 编译器在编译时而非运行时检查类型。
- 消除显式转换 — 代码变得更简洁、更易读。
- 可复用性 — 一个类或函数可以适用于不同的类型。
基本语法:类、函数与约束
泛型类
最简单的入门方法是声明一个带有一个或多个类型参数的类。类型参数在尖括号 <T> 中指定(字母可以是任何值,但传统上使用 T、E、K、V):
class Box<T>(val value: T)
fun main() { val stringBox = Box("Kotlin") // 类型自动推断 val intBox = Box(42) // Box<Int>
println(stringBox.value.length) // 无需转换即可工作! println(intBox.value + 1) // 43}Kotlin 可以推断类型,因此不需要写 Box<String> — 写 Box("Kotlin") 就足够了。
泛型函数
函数也可以是泛型的。类型参数在函数名之前指定:
fun <T> getMiddleElement(list: List<T>): T? { if (list.isEmpty()) return null return list[list.size / 2]}
fun main() { val numbers = listOf(1, 2, 3, 4, 5) val middle = getMiddleElement(numbers) // 类型 Int? 自动推断 println(middle) // 3}约束(where / 上界)
有时您需要限制泛型中可使用的类型。例如,要调用 .compareTo() 方法,类型必须实现 Comparable 接口:
fun <T : Comparable<T>> maxOfTwo(a: T, b: T): T { return if (a >= b) a else b}
fun main() { println(maxOfTwo(10, 20)) // 20 println(maxOfTwo("A", "B")) // B // maxOfTwo(10, "A") // 编译错误:类型不匹配}如果需要多个约束,请使用 where 关键字:
interface Printable { fun print()}
fun <T> process(item: T) where T : Comparable<T>, T : Printable