泛型类型约束简介
之前我们已经了解了,泛型可以接受任意类型的参数,使代码具有良好的复用性。现在,我们来学习泛型的另一个方面:类型约束(type bounds)。
有时候,我们希望对泛型函数或类中允许的类型参数进行限制。例如,我们有一个泛型类 Storage<T>
,但我们只想让它存储“书籍”类型的对象,而不需要创建另一个专门的类。这种情况下,就可以使用类型约束。
在类中使用类型约束
假设我们有以下泛型类:
class Storage<T>() {
// 一些代码
}
解释: 这是一个通用存储类,可以保存任何类型的对象。
如果我们只希望这个类保存书籍,而“书籍”可以包括杂志、手册等,那么我们可以通过添加类型约束 T : Book
来限制只允许 T
是 Book
或其子类:
class Storage<T : Book>() {
// 一些代码
}
解释: 通过
T : Book
,我们限制了泛型T
必须是Book
类型或其子类。这样我们就可以避免误地将非书籍类型存入该类中。
创建类并使用泛型类
open class Book {}
class Magazine : Book() {}
class Stone {}
解释: 我们创建了三个类:
Book
是一个父类,Magazine
继承自Book
,Stone
与Book
毫无关系。
然后我们尝试如下创建泛型类实例:
val storage1 = Storage<Book>() // 合法
val storage2 = Storage<Magazine>() // 合法(Magazine 是 Book 的子类)
val storage3 = Storage<Stone>() // 编译错误
解释: 前两个实例是合法的,因为类型满足约束。第三个会报错:类型参数
Stone
不在它的界限内,这是编译时错误,有助于我们提前捕捉潜在问题。
默认情况下,所有泛型类型参数的上界是
Any?
(允许空值)。也就是说SomeGeneric<T>
默认等价于SomeGeneric<T : Any?>
。
作为约束,可以使用类或接口,但不能试图让一个泛型类继承另一个(如
Storage<Magazine> : Storage<Book>
),这是不允许的。
在函数中使用类型约束
我们也可以在泛型函数中使用类型约束,语法类似:
fun <T : Book> sortByDate(list: List<T>) { ... }
解释: 这个函数只接受泛型为
Book
或其子类的List
参数。
假设有两个列表:
val listOne: List<Magazine> = listOf()
val listTwo: List<String> = listOf()
sortByDate(listOne) // 合法,Magazine 是 Book 的子类
sortByDate(listTwo) // 错误,String 不是 Book 的子类
明确不可为空的类型(Definitely non-nullable types)
Kotlin 1.7 起支持:明确不可为空的类型,用于和 Java 的互操作。语法是:T & Any
(T
与 Any
的交集类型)。
前提:类型参数的上界必须是可空类型(如
Any?
或String?
)。
示例 Java 接口:
public interface Game<T> {
public T save(T x);
@NotNull
public T load(@NotNull T x);
}
解释: Java 中使用了
@NotNull
注解,说明load()
不允许接收或返回 null。
Kotlin 中实现:
interface ArcadeGame<T1> : Game<T1> {
override fun save(x: T1): T1
override fun load(x: T1 & Any): T1 & Any // 正确用法
// override fun load(x: T1): T1 // 编译失败
}
解释: 使用
T1 & Any
声明T1
绝对不能为 null,从而符合 Java 中的@NotNull
要求。
Kotlin 示例:Elvis 运算符风格函数
fun <T : String?> elvisLike(first: T, second: T & Any): T & Any = first ?: second
使用示例:
elvisLike<String>("", "123").length // 结果为 0
elvisLike<String>("", null).length // 编译错误,null 不能传给 non-null 参数
elvisLike<String?>(null, "123").length // 结果为 3
elvisLike<String?>(null, null).length // 编译错误,null 是非法参数
解释:
elvisLike
函数模拟 Elvis 操作符行为:first
可能为 null,但second
必须是非空的。这样可通过编译时确保安全。
多重约束(Multiple Bounds)
泛型变量可以有多个类型约束,但只有一个可以写在 <T>
中,其它的必须使用 where
子句。
示例:
fun <T> sortByDate(list: List<T>)
where T : Book, T : Watchable<T> { ... }
解释: 类型参数
T
必须既是Book
的子类,又实现Watchable<T>
接口。
注意事项:
Kotlin 和 Java 一样 不支持多继承(类只能继承一个父类);
但类可以实现多个接口,因此多个接口约束是允许的。
总结
类型约束用于限制泛型参数类型。
最常见的是 上界约束(
T : SomeType
)。类型约束提高了代码的安全性和可读性。
Kotlin 支持:
单个约束
多个接口约束(使用
where
子句)明确不可为空类型(
T & Any
)
这使得 Kotlin 泛型更加强大、类型安全且灵活。