Kotlin泛型约束

发布于:2025-07-20 ⋅ 阅读:(15) ⋅ 点赞:(0)

泛型类型约束简介

之前我们已经了解了,泛型可以接受任意类型的参数,使代码具有良好的复用性。现在,我们来学习泛型的另一个方面:类型约束(type bounds)
有时候,我们希望对泛型函数或类中允许的类型参数进行限制。例如,我们有一个泛型类 Storage<T>,但我们只想让它存储“书籍”类型的对象,而不需要创建另一个专门的类。这种情况下,就可以使用类型约束。


在类中使用类型约束

假设我们有以下泛型类:

class Storage<T>() {
    // 一些代码
}

解释: 这是一个通用存储类,可以保存任何类型的对象。

如果我们只希望这个类保存书籍,而“书籍”可以包括杂志、手册等,那么我们可以通过添加类型约束 T : Book 来限制只允许 TBook 或其子类:

class Storage<T : Book>() {
    // 一些代码
}

解释: 通过 T : Book,我们限制了泛型 T 必须是 Book 类型或其子类。这样我们就可以避免误地将非书籍类型存入该类中。


创建类并使用泛型类

open class Book {}
class Magazine : Book() {}
class Stone {}

解释: 我们创建了三个类:Book 是一个父类,Magazine 继承自 BookStoneBook 毫无关系。

然后我们尝试如下创建泛型类实例:

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 & AnyTAny 的交集类型)。

前提:类型参数的上界必须是可空类型(如 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 泛型更加强大、类型安全且灵活。


网站公告

今日签到

点亮在社区的每一天
去签到