Kotlin中的泛型:为什么需要以及如何实际使用
泛型是Kotlin语言的关键特性之一,它允许编写更灵活、类型安全且可重用的代码。如果你曾经写过List<String>或Map<Int, User>,那么你已经接触过泛型。但它们实际上为什么需要,以及如何充分利用它们?在本文中,我们将分析理论,并通过实际示例进入实践。
泛型解决的问题
假设你需要编写一个函数,返回列表中的第一个元素。没有泛型,你必须为每种数据类型编写单独的函数:
fun getFirstString(list: List<String>): String? = list.firstOrNull()fun getFirstInt(list: List<Int>): Int? = list.firstOrNull()fun getFirstUser(list: List<User>): User? = list.firstOrNull()这会导致代码重复。使用Any可以解决问题,但你会丢失编译时的类型信息:
fun getFirst(list: List<Any>): Any? = list.firstOrNull()
// 必须进行类型转换val name: String? = getFirst(strings) as? String泛型优雅地解决了这个问题:
fun <T> getFirst(list: List<T>): T? = list.firstOrNull()
// 编译器知道类型!val name: String? = getFirst(listOf("Alice", "Bob"))val age: Int? = getFirst(listOf(25, 30))现在一个函数可以处理任何类型,同时保持完全的类型安全。
Kotlin中泛型的基础:语法和声明
泛型使用尖括号<T>声明。字母T只是一个约定,你可以使用任何名称:E(元素)、K(键)、V(值)。
泛型类
class Box<T>(val value: T) { fun getContent(): T = value}
fun main() { val stringBox = Box("Hello") val intBox = Box(42) println(stringBox.getContent()) // Hello println(intBox.getContent()) // 42}泛型函数
fun <T> printItem(item: T) { println("Item: $item")}
// 可以显式指定类型printItem<String>("Kotlin")// 或者依赖类型推断printItem(100)泛型接口
interface Repository<T> { fun getById(id: Int): T fun save(item: T): Boolean}
class UserRepository : Repository<User> { override fun getById(id: Int): User { /* ... */ } override fun save(item: User): Boolean { /* ... */ }}变型:in、out和星号*
Kotlin最强大的特性之一是变型。它回答的问题是:List<String>可以是List<Any>的子类型吗?
协变(out)
当你的泛型类型只被返回(生产者)时,使用out。例如,List<T>是协变的:
// 你可以将List<String>赋值给List<Any>变量val strings: List<String> = listOf("a", "b")val anyList: List<Any> = strings // 可以!
// 但不能添加元素// anyList.add(1) // 编译错误!自定义示例:
class Producer<out T>(private val value: T) { fun produce(): T = value}
fun main() { val stringProducer: Producer<String> = Producer("Hello") val anyProducer: Producer<Any> = stringProducer // 可以工作! println(anyProducer.produce())}逆变(in)
当类型只被接受(消费者)时,使用in。例如,Comparable<T>:
博客
书籍推荐
推荐阅读