3、泛型类与接口:声明泛型类、泛型接口、泛型约束
泛型这东西,说白了就是「类型占位符」。
我刚开始学 Kotlin 时,觉得泛型不就是 Java 那套吗?后来写多了才发现,Kotlin 的泛型设计更严谨,也更灵活。尤其是泛型约束,用好了能让代码既安全又优雅。
3.1 声明泛型类
先看一个最简单的例子。假设我们要做一个盒子,能装任何类型的东西。
class Box<T>(val item: T)
fun main() {
val stringBox = Box("Hello")
val intBox = Box(42)
}
这里的 T 就是类型参数。你传什么类型,它就变成什么类型。
我在项目中遇到过这样的场景:需要封装一个网络请求的结果,可能是成功的数据,也可能是错误信息。用泛型类就很合适。
sealed class Result<out T> {
data class Success<T>(val data: T) : Result<T>()
data class Error(val message: String) : Result<Nothing>()
}
嗯,这里用了 out 关键字,后面会讲到协变。先记住这个模式就行。
3.2 声明泛型接口
泛型接口和泛型类类似。比如定义一个仓库接口,能存能取。
interface Repository<T> {
fun save(item: T)
fun findById(id: String): T?
}
实现的时候,你可以指定具体类型:
class UserRepository : Repository<User> {
private val users = mutableMapOf<String, User>()
override fun save(item: User) {
users[item.id] = item
}
override fun findById(id: String): User? {
return users[id]
}
}
你想想看,如果没有泛型,你得为每种类型写一个接口,那得多累?
3.3 泛型约束
泛型约束,就是限制类型参数的范围。说白了,你不能什么类型都往里塞。
3.3.1 上界约束
最常见的约束是上界约束,用 : 指定。
class NumberBox<T : Number>(val item: T)
fun main() {
val intBox = NumberBox(10) // 可以
val doubleBox = NumberBox(3.14) // 可以
// val stringBox = NumberBox("Hi") // 编译错误!
}
这里 T : Number 表示 T 必须是 Number 的子类。String 不是 Number 的子类,所以编译不通过。
我曾经在项目中犯过一个错:没有加约束,结果运行时类型转换炸了。从那以后,我写泛型必加约束,哪怕只是 Any。
3.3.2 多个上界
有时候一个约束不够。比如你要 T 既是 Comparable 又是 Serializable。
class MultiBound<T>(val item: T) where T : Comparable<T>, T : Serializable
语法有点特别:where 关键字后面跟多个约束,用逗号隔开。
我个人习惯把 where 放在类名后面,这样看起来更清晰:
class MultiBound<T>(val item: T)
where T : Comparable<T>,
T : Serializable
3.3.3 可空类型约束
默认情况下,泛型参数是可空的。如果你不想让它为空,可以加 Any 约束。
class NonNullBox<T : Any>(val item: T)
fun main() {
val box = NonNullBox("Hello") // 可以
// val nullBox = NonNullBox(null) // 编译错误!
}
这个细节很容易被忽略。我见过不少同事写的泛型类,传 null 进去直接崩了。加个 : Any 就能避免。
reified 关键字,这个后面会讲。
3.4 实战:泛型约束的应用
来看一个实际例子。假设我们要写一个通用的排序工具,只对实现了 Comparable 的类型生效。
fun <T : Comparable<T>> List<T>.sortedDescending(): List<T> {
return this.sortedByDescending { it }
}
fun main() {
val numbers = listOf(3, 1, 4, 1, 5, 9)
println(numbers.sortedDescending()) // [9, 5, 4, 3, 1, 1]
}
这里 T : Comparable<T> 保证了只有可比较的类型才能调用这个方法。如果你传一个没有实现 Comparable 的类,编译直接报错。
你想想看,这比运行时抛异常强多了吧?
3.5 泛型约束与类型安全
泛型约束的核心价值,就是让编译器帮你检查类型。我总结了一个原则:
举个例子,假设你要写一个函数,只接受数字类型的集合:
fun <T : Number> sum(list: List<T>): Double {
return list.sumOf { it.toDouble() }
}
fun main() {
println(sum(listOf(1, 2, 3))) // 6.0
// println(sum(listOf("a", "b"))) // 编译错误!
}
没有泛型约束的话,你可能会写 List<Any>,然后在函数内部做类型检查。那样既啰嗦又不安全。
我曾经接手过一个老项目,里面全是 List<Any> 和 as? Type 的转换。改起来那叫一个痛苦。所以我现在写新代码,泛型约束能加就加,绝不偷懒。
3.6 小结
这一章我们聊了:
- 泛型类:用
<T>声明类型参数 - 泛型接口:接口也能带类型参数,实现时指定具体类型
- 泛型约束:用
:和where限制类型范围 - 实战应用:约束让代码更安全,错误更早暴露
下一章我们会深入泛型的高级特性——协变与逆变。到时候你会发现,Kotlin 的泛型设计比 Java 优雅得多。
嗯,今天就到这里。记得多写多练,泛型这东西,光看是学不会的。