前言
在 Kotlin 中,泛型是一种强大的特性,它允许你编写可复用的代码,这些代码可以处理不同类型的数据,而不需要为每种数据类型都编写特定的实现。
泛型在类、接口、函数等方面都有广泛应用,下面将详细介绍 Kotlin 中泛型的相关内容。
泛型类和泛型接口
泛型类和泛型接口在定义时会使用类型参数,这些类型参数在创建类的实例或实现接口时会被具体的类型所替代。
class
1 | class ResultVo<T> { |
data class
1 | data class ResultVo<out T>( |
interface
1 | interface Repository<T> { |
泛型函数
泛型函数允许你在函数级别使用类型参数,使得函数可以处理不同类型的数据。
示例代码
1 | fun <T> printItem(item: T) { |
代码解释
printItem<T>是一个泛型函数,<T>表示该函数使用了一个类型参数T。在调用printItem函数时,Kotlin 会根据传入的参数自动推断T的具体类型。
在 Kotlin 中,当你使用泛型函数并希望访问传入对象的属性时,不能直接访问任意属性,因为编译器不知道泛型类型 T 具体是什么。为了解决这个问题,通常有以下几种方式:
泛型获取属性
使用泛型约束(推荐)
通过给泛型添加上界(upper bound),限定 T 必须是某个类或接口的子类型,这样就可以安全地访问该类/接口中定义的属性。
示例:
1 | // 定义一个接口或开放类 |
Tip:
💡 你可以用
class、interface或者Any作为上界,但只有上界中声明的成员才能被访问。
使用反射
不推荐用于常规逻辑
如果你无法控制传入类型的结构(比如完全通用的工具函数),可以使用 Kotlin 反射来动态获取属性。但这会带来性能开销,并且需要额外依赖 kotlin-reflect。
示例:
1 | import kotlin.reflect.full.memberProperties |
注意:
反射应尽量避免在性能敏感或高频调用的代码中使用。
总结
| 方法 | 是否推荐 | 说明 |
|---|---|---|
| 泛型上界约束 | ✅ 强烈推荐 | 类型安全、性能好、代码清晰 |
| 反射 | ⚠️ 谨慎使用 | 灵活但慢,需额外依赖 |
泛型约束
泛型约束允许你限制类型参数必须是某个特定类型或其子类型,这样可以在泛型代码中使用特定类型的方法和属性。
示例代码
1 | // 定义一个接口 |
代码解释
Printable是一个接口,Person类实现了该接口。printAll<T : Printable>是一个泛型函数,使用了泛型约束T : Printable,表示类型参数T必须是Printable接口的实现类。在函数内部,可以调用T类型对象的printInfo方法。
变型(协变、逆变和不变)
Kotlin 中的变型用于处理泛型类型之间的子类型关系,分为协变、逆变和不变三种情况。
协变(out)
协变允许泛型类型作为生产者,只能从中读取数据,不能写入数据。
示例代码
1 | // 协变泛型类 |
代码解释
Producer<out T>是一个协变泛型接口,使用out关键字表示协变。StringProducer类实现了Producer<String>接口。- 由于协变的特性,
Producer<String>类型的对象可以赋值给Producer<Any>类型的变量。
逆变(in)
逆变允许泛型类型作为消费者,只能向其中写入数据,不能读取数据。
示例代码
1 | // 逆变泛型类 |
代码解释
Consumer<in T>是一个逆变泛型接口,使用in关键字表示逆变。AnyConsumer类实现了Consumer<Any>接口。- 由于逆变的特性,
Consumer<Any>类型的对象可以赋值给Consumer<String>类型的变量。
不变
如果泛型类型既不是协变也不是逆变,则是不变的,即泛型类型之间不存在子类型关系。
星投影(*)
星投影用于在不知道具体类型参数时使用泛型类型。
示例代码
1 | fun printElements(list: List<*>) { |
代码解释
List<*>表示一个泛型列表,其中的元素类型未知。printElements函数可以接受任何类型的列表作为参数。
通过以上介绍,你可以了解到 Kotlin 中泛型的基本概念和使用方法,包括泛型类、泛型接口、泛型函数、泛型约束、变型和星投影等。泛型可以提高代码的复用性和类型安全性,是 Kotlin 中非常重要的特性之一。