ZetCode

Kotlin where 关键字

最后修改于 2025 年 4 月 19 日

Kotlin 的泛型类型系统允许使用多个需求来约束类型参数。where 关键字指定这些约束。本教程通过实际示例深入探讨了 where 关键字。

基本定义

Kotlin 中的 where 关键字将多个约束应用于泛型类型参数。当一个类型参数必须满足多个条件时,会使用它。约束可以包括类/接口实现和其他类型关系。

使用 where 的单个约束

where 的最简单用法是将一个约束应用于类型参数。这等效于使用冒号语法,但演示了基本结构。

SingleConstraint.kt
package com.zetcode

interface Printable {
    fun print()
}

class Document : Printable {
    override fun print() = println("Printing document")
}

fun <T> printItem(item: T) where T : Printable {
    item.print()
}

fun main() {
    val doc = Document()
    printItem(doc) // Output: Printing document
}

这里我们定义了一个泛型函数 printItem,它要求其类型参数 T 实现 Printable 接口。where 子句强制执行此约束。Document 类满足此要求。

对单个类型的多个约束

当您需要对一个类型参数进行多个约束时,where 关键字就会发挥作用。此示例要求一个类型实现两个接口。

MultipleConstraints.kt
package com.zetcode

interface Serializable {
    fun serialize(): String
}

interface Deserializable {
    fun deserialize(data: String)
}

class Config : Serializable, Deserializable {
    override fun serialize() = "Config data"
    override fun deserialize(data: String) = println("Loading: $data")
}

fun <T> processData(item: T) where T : Serializable, T : Deserializable {
    val data = item.serialize()
    item.deserialize(data)
}

fun main() {
    val config = Config()
    processData(config) // Output: Loading: Config data
}

processData 函数要求其类型参数 T 同时实现 Serializable 和 Deserializable 接口。Config 类满足这些要求,因此我们可以使用 Config 实例调用该函数。

对多个类型参数的约束

where 可以在单个声明中约束多个类型参数。当类型需要以特定方式相互关联时,这很有用。

MultiTypeConstraints.kt
package com.zetcode

interface Producer<out T> {
    fun produce(): T
}

interface Consumer<in T> {
    fun consume(item: T)
}

fun <T, U> transform(
    producer: Producer<T>,
    consumer: Consumer<U>
) where T : U, U : Number {
    val item = producer.produce()
    consumer.consume(item)
}

class IntProducer : Producer<Int> {
    override fun produce() = 42
}

class NumberConsumer : Consumer<Number> {
    override fun consume(item: Number) = println("Consumed: $item")
}

fun main() {
    transform(IntProducer(), NumberConsumer()) // Output: Consumed: 42
}

此示例显示了对两个类型参数 T 和 U 的约束。T 必须是 U 的子类型,并且 U 必须是 Number 的子类型。transform 函数只能使用满足这些关系的类型调用。

带有 where 约束的类

where 关键字也可以与类声明一起使用,以约束它们的类型参数。这确保了所有类方法都可以访问受约束的类型。

ClassConstraints.kt
package com.zetcode

interface Identifiable {
    val id: String
}

interface Timestamped {
    val timestamp: Long
}

class Repository<T>(private val items: List<T>) where T : Identifiable, T : Timestamped {
    fun findById(id: String): T? = items.find { it.id == id }
    
    fun getRecent(): List<T> {
        val now = System.currentTimeMillis()
        return items.filter { now - it.timestamp < 3600000 }
    }
}

data class LogEntry(
    override val id: String,
    override val timestamp: Long,
    val message: String
) : Identifiable, Timestamped

fun main() {
    val logs = listOf(
        LogEntry("1", System.currentTimeMillis() - 1000, "Started"),
        LogEntry("2", System.currentTimeMillis() - 7200000, "Old entry")
    )
    
    val repo = Repository(logs)
    println(repo.findById("1")?.message) // Output: Started
    println(repo.getRecent().size)       // Output: 1
}

Repository 类要求其类型参数 T 同时实现 Identifiable 和 Timestamped 接口。这允许类方法安全地访问 id 和 timestamp 属性。LogEntry 满足这些约束。

组合类和函数约束

当类和函数都有 where 约束时,它们会组合起来创建更严格的类型要求。这提供了对泛型类型的精细控制。

CombinedConstraints.kt
package com.zetcode

interface Named {
    val name: String
}

interface Priced {
    val price: Double
}

class Store<T> where T : Named {
    private val items = mutableListOf<T>()
    
    fun addItem(item: T) = items.add(item)
    
    fun <U> findCheaperThan(maxPrice: Double): List<U> 
        where U : T, U : Priced {
        return items.filterIsInstance<U>().filter { it.price <= maxPrice }
    }
}

data class Product(
    override val name: String,
    override val price: Double
) : Named, Priced

fun main() {
    val store = Store<Named>()
    store.addItem(Product("Laptop", 999.99))
    store.addItem(Product("Mouse", 25.50))
    
    val affordable = store.findCheaperThan<Product>(100.0)
    println(affordable.map { it.name }) // Output: [Mouse]
}

Store 类将 T 约束为 Named,而其 findCheaperThan 方法进一步要求 U 既是 T 的子类型又实现 Priced 接口。这确保了我们可以在过滤结果中访问 name 和 price 属性。

递归类型约束

where 子句可以表示递归类型约束,其中类型参数必须以特定方式与自身相关联。这对于比较操作很有用。

RecursiveConstraints.kt
package com.zetcode

interface Comparable<in T> {
    fun compareTo(other: T): Int
}

fun <T> max(a: T, b: T): T where T : Comparable<T> {
    return if (a.compareTo(b) >= 0) a else b
}

data class Version(val major: Int, val minor: Int) : Comparable<Version> {
    override fun compareTo(other: Version): Int {
        return when {
            major != other.major -> major - other.major
            else -> minor - other.minor
        }
    }
}

fun main() {
    val v1 = Version(2, 5)
    val v2 = Version(2, 7)
    println(max(v1, v2)) // Output: Version(major=2, minor=7)
}

max 函数要求 T 实现 Comparable<T> 接口,这意味着 T 的实例可以与其他 T 的实例进行比较。Version 通过实现 compareTo 方法来满足此约束。

复杂的多类型关系

where 最强大的用途是建立多个类型参数之间的复杂关系。此示例显示了一个具有多个约束的数据处理器。

ComplexRelationships.kt
package com.zetcode

interface Entity<ID> {
    val id: ID
}

interface Repository<E, ID> where E : Entity<ID>, ID : Comparable<ID> {
    fun save(entity: E): E
    fun findById(id: ID): E?
}

data class User(
    override val id: String,
    val name: String
) : Entity<String>

class UserRepository : Repository<User, String> {
    private val storage = mutableMapOf<String, User>()
    
    override fun save(entity: User): User {
        storage[entity.id] = entity
        return entity
    }
    
    override fun findById(id: String) = storage[id]
}

fun main() {
    val repo = UserRepository()
    val user = repo.save(User("123", "Alice"))
    println(repo.findById("123")?.name) // Output: Alice
}

此示例表明 E 必须是 Entity<ID> 并且 ID 必须是 Comparable<ID>。UserRepository 使用 String 作为 ID 和 User 作为 E 来实现此接口。String 是可比较的,User 实现了 Entity<String>。

where 子句的最佳实践

来源

Kotlin 泛型文档

本教程深入介绍了 Kotlin 的 where 关键字,展示了如何将多个约束应用于泛型类型参数。我们探索了从简单到复杂类型关系的各种场景。正确使用约束可以使您的泛型代码更具类型安全性,同时保持灵活性。

作者

我叫 Jan Bodnar,是一位充满激情的程序员,拥有多年的编程经验。自 2007 年以来,我一直在撰写编程文章。到目前为止,我写了 1400 多篇文章和 8 本电子书。我拥有超过八年的编程教学经验。

列出 所有 Kotlin 教程