Kotlin where 关键字
最后修改于 2025 年 4 月 19 日
Kotlin 的泛型类型系统允许使用多个需求来约束类型参数。where
关键字指定这些约束。本教程通过实际示例深入探讨了 where
关键字。
基本定义
Kotlin 中的 where
关键字将多个约束应用于泛型类型参数。当一个类型参数必须满足多个条件时,会使用它。约束可以包括类/接口实现和其他类型关系。
使用 where 的单个约束
where
的最简单用法是将一个约束应用于类型参数。这等效于使用冒号语法,但演示了基本结构。
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
关键字就会发挥作用。此示例要求一个类型实现两个接口。
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
可以在单个声明中约束多个类型参数。当类型需要以特定方式相互关联时,这很有用。
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
关键字也可以与类声明一起使用,以约束它们的类型参数。这确保了所有类方法都可以访问受约束的类型。
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
约束时,它们会组合起来创建更严格的类型要求。这提供了对泛型类型的精细控制。
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
子句可以表示递归类型约束,其中类型参数必须以特定方式与自身相关联。这对于比较操作很有用。
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
最强大的用途是建立多个类型参数之间的复杂关系。此示例显示了一个具有多个约束的数据处理器。
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 子句的最佳实践
- 用于多个约束: 当您需要对类型参数进行多个约束时,
where
关键字最有价值。 - 提高可读性: 与内联约束相比,
where
通常使复杂的泛型声明更具可读性。 - 考虑类型安全: 设计良好的约束在编译时而不是运行时捕获类型错误。
- 平衡灵活性: 除非您的实现需要,否则避免过度约束类型。
- 记录约束: 复杂的约束应该有详细的文档,以帮助其他开发人员了解需求。
来源
本教程深入介绍了 Kotlin 的 where
关键字,展示了如何将多个约束应用于泛型类型参数。我们探索了从简单到复杂类型关系的各种场景。正确使用约束可以使您的泛型代码更具类型安全性,同时保持灵活性。
作者
列出 所有 Kotlin 教程。