ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Kotlin 泛型:从基础语法到高级实战

Kotlin 泛型:从基础语法到高级实战 1. 引言泛型是 Kotlin 语言中非常强大的特性之一它允许我们在定义类、接口和函数时使用类型参数从而编写出更通用、更安全的代码。本文将从基础语法入手逐步深入到协变、逆变、星投影等高级主题并通过丰富的代码实例帮助读者彻底掌握 Kotlin 泛型。2. 为什么需要泛型在没有泛型的情况下我们通常需要借助Any类型或强制类型转换来实现通用逻辑但这会带来类型安全问题。例如下面的代码在运行时可能抛出ClassCastException// 不使用泛型存在类型安全隐患 class Box(val item: Any) fun main() { val box Box(Hello) val number: Int box.item as Int // 运行时崩溃ClassCastException println(number) }使用泛型后编译器可以在编译期就发现类型不匹配的问题从而避免运行时异常// 使用泛型编译期类型检查 class BoxT(val item: T) fun main() { val box Box(Hello) // val number: Int box.item // 编译错误类型不匹配 val text: String box.item // 正确 println(text) }3. 泛型类与泛型接口在 Kotlin 中我们可以通过在类名或接口名后面加上尖括号T来声明类型参数。下面是一个泛型类的示例// 泛型类一个简单的栈实现 class StackT { private val elements mutableListOfT() fun push(item: T) { elements.add(item) } fun pop(): T? { if (elements.isEmpty()) return null return elements.removeAt(elements.size - 1) } fun peek(): T? elements.lastOrNull() fun isEmpty(): Boolean elements.isEmpty() } fun main() { val stack StackInt() stack.push(1) stack.push(2) stack.push(3) println(stack.pop()) // 输出3 println(stack.peek()) // 输出2 }泛型接口与泛型类的声明方式类似下面是一个泛型接口的示例// 泛型接口定义一个可比较的接口 interface ComparableT { fun compareTo(other: T): Int } // 实现泛型接口 class Person(val name: String, val age: Int) : ComparablePerson { override fun compareTo(other: Person): Int { return this.age - other.age } } fun main() { val alice Person(Alice, 25) val bob Person(Bob, 30) println(alice.compareTo(bob)) // 输出-5 }4. 泛型函数除了类和接口Kotlin 还支持泛型函数。泛型函数允许我们在函数级别使用类型参数从而编写出更灵活的工具函数。下面是一个泛型函数的示例// 泛型函数返回列表的最后一个元素 fun T ListT.lastElement(): T? { return if (this.isEmpty()) null else this[this.size - 1] } fun main() { val numbers listOf(1, 2, 3, 4, 5) println(numbers.lastElement()) // 输出5 val names listOf(Alice, Bob, Charlie) println(names.lastElement()) // 输出Charlie }泛型函数也可以有多个类型参数下面是一个交换两个变量值的示例// 多个类型参数的泛型函数 fun A, B PairA, B.swap(): PairB, A { return Pair(this.second, this.first) } fun main() { val pair Pair(key, 42) val swapped pair.swap() println(swapped) // 输出(42, key) }5. 类型约束有时候我们希望限制类型参数的上界只允许特定类型或其子类作为类型实参。Kotlin 使用冒号:来声明类型约束。下面是一个带类型约束的泛型函数示例// 类型约束T 必须是 Number 的子类型 fun T : Number sum(list: ListT): Double { var total 0.0 for (item in list) { total item.toDouble() } return total } fun main() { val ints listOf(1, 2, 3) val doubles listOf(1.5, 2.5, 3.5) println(sum(ints)) // 输出6.0 println(sum(doubles)) // 输出7.5 // println(sum(listOf(a, b))) // 编译错误String 不是 Number 的子类型 }Kotlin 还支持多个类型约束使用where关键字// 多个类型约束T 必须同时实现 Comparable 和 CharSequence fun T maxOf(list: ListT) where T : ComparableT, T : CharSequence { // 函数体 } // 或者使用 where 子句的另一种写法 fun T printSorted(list: ListT) where T : ComparableT, T : Any { val sorted list.sorted() println(sorted) } fun main() { val words listOf(banana, apple, cherry) printSorted(words) // 输出[apple, banana, cherry] }6. 协变与逆变协变和逆变是泛型中比较抽象的概念它们解决了泛型类型之间的继承关系问题。Kotlin 使用out和in关键字来声明协变和逆变。6.1 协变out当一个泛型类只把类型参数用作输出即返回值时可以将其声明为协变。协变意味着ProducerSub是ProducerSuper的子类型。// 协变out 关键字 interface Producerout T { fun produce(): T } class StringProducer : ProducerString { override fun produce(): String Hello } fun main() { val stringProducer: ProducerString StringProducer() // 协变ProducerString 可以赋值给 ProducerAny val anyProducer: ProducerAny stringProducer println(anyProducer.produce()) // 输出Hello }6.2 逆变in当一个泛型类只把类型参数用作输入即参数时可以将其声明为逆变。逆变意味着ConsumerSuper是ConsumerSub的子类型。// 逆变in 关键字 interface Consumerin T { fun consume(item: T) } class AnyConsumer : ConsumerAny { override fun consume(item: Any) { println(Consumed: $item) } } fun main() { val anyConsumer: ConsumerAny AnyConsumer() // 逆变ConsumerAny 可以赋值给 ConsumerString val stringConsumer: ConsumerString anyConsumer stringConsumer.consume(Hello) // 输出Consumed: Hello }6.3 声明处变型与使用处变型Kotlin 支持两种变型方式声明处变型declaration-site variance和使用处变型use-site variance。声明处变型是在类或接口声明时使用out或in而使用处变型是在使用泛型类型时通过类型投影来实现。// 使用处变型类型投影 fun copy(from: Arrayout Any, to: ArrayAny) { for (i in from.indices) { to[i] from[i] } } fun main() { val intArray: ArrayInt arrayOf(1, 2, 3) val anyArray: ArrayAny arrayOfNullsAny(3) copy(intArray, anyArray) // 类型投影允许传入 ArrayInt println(anyArray.contentToString()) // 输出[1, 2, 3] }7. 星投影星投影star projection用于表示未知的类型参数。对于Fooout T星投影等价于Fooout Any?对于Fooin T星投影等价于Fooin Nothing。星投影在只关心泛型类型本身而不关心具体类型参数时非常有用。// 星投影示例 fun printList(list: List*) { for (item in list) { println(item) } } fun main() { val intList: ListInt listOf(1, 2, 3) val stringList: ListString listOf(a, b) printList(intList) // 输出1 2 3 printList(stringList) // 输出a b }8. 泛型与空安全Kotlin 的泛型与空安全机制可以很好地结合。默认情况下类型参数T是可空的即T?是允许的。如果需要限制类型参数为非空可以使用T : Any约束。// 泛型与空安全 class NonNullBoxT : Any(val item: T) fun main() { val box NonNullBox(Hello) // val nullBox NonNullBox(null) // 编译错误null 不是 Any 的子类型 println(box.item) } // 可空类型参数示例 class NullableBoxT(val item: T?) fun main2() { val box NullableBoxString(null) println(box.item) // 输出null }9. 泛型与反射在 Kotlin 中泛型类型信息在运行时会被擦除type erasure但我们可以通过reified关键字和内联函数来保留类型信息。这在编写需要类型信息的工具函数时非常有用。// 使用 reified 关键字保留类型信息 inline fun reified T isInstance(value: Any): Boolean { return value is T } fun main() { println(isInstanceString(Hello)) // 输出true println(isInstanceInt(Hello)) // 输出false } // 获取泛型类型的 Class 对象 inline fun reified T typeName(): String { return T::class.java.simpleName } fun main2() { println(typeNameString()) // 输出String println(typeNameInt()) // 输出Integer }10. 实战案例泛型数据仓库下面通过一个综合案例来展示泛型在实际项目中的应用。我们将实现一个泛型数据仓库支持不同类型的数据存取。// 泛型数据仓库接口 interface RepositoryT, ID { fun findById(id: ID): T? fun findAll(): ListT fun save(entity: T): T fun deleteById(id: ID) } // 内存实现 class InMemoryRepositoryT, ID( private val idExtractor: (T) - ID ) : RepositoryT, ID { private val storage mutableMapOfID, T() override fun findById(id: ID): T? storage[id] override fun findAll(): ListT storage.values.toList() override fun save(entity: T): T { val id idExtractor(entity) storage[id] entity return entity } override fun deleteById(id: ID) { storage.remove(id) } } // 用户实体 data class User(val id: Long, val name: String, val email: String) fun main() { val userRepository InMemoryRepositoryUser, Long { it.id } userRepository.save(User(1, Alice, aliceexample.com)) userRepository.save(User(2, Bob, bobexample.com)) val allUsers userRepository.findAll() println(allUsers) // 输出[User(id1, nameAlice, emailaliceexample.com), User(id2, nameBob, emailbobexample.com)] val user userRepository.findById(1) println(user) // 输出User(id1, nameAlice, emailaliceexample.com) userRepository.deleteById(2) println(userRepository.findAll()) // 输出[User(id1, nameAlice, emailaliceexample.com)] }11. 总结本文系统地介绍了 Kotlin 泛型的核心概念和实战技巧包括泛型类、泛型接口、泛型函数、类型约束、协变与逆变、星投影、空安全结合以及反射中的类型保留等。通过丰富的代码示例相信读者已经对 Kotlin 泛型有了深入的理解。在实际开发中合理运用泛型可以显著提升代码的复用性和类型安全性建议在项目中多加实践。
返回列表