Kotlin的语法入门-泛型

前言

在 Kotlin 中,泛型是一种强大的特性,它允许你编写可复用的代码,这些代码可以处理不同类型的数据,而不需要为每种数据类型都编写特定的实现。

泛型在类、接口、函数等方面都有广泛应用,下面将详细介绍 Kotlin 中泛型的相关内容。

泛型类和泛型接口

泛型类和泛型接口在定义时会使用类型参数,这些类型参数在创建类的实例或实现接口时会被具体的类型所替代。

class

1
2
3
4
5
class ResultVo<T> {
var code: Int = 0
var msg: String = ""
var obj: T? = null
}

data class

1
2
3
4
5
data class ResultVo<out T>(
val code: Int,
val msg: String,
val obj: T?
)

interface

1
2
3
4
5
interface Repository<T> {
fun save(item: T)
fun findById(id: Int): T?
fun findAll(): List<T>
}

泛型函数

泛型函数允许你在函数级别使用类型参数,使得函数可以处理不同类型的数据。

示例代码

1
2
3
4
5
6
7
8
fun <T> printItem(item: T) {
println(item)
}

fun main() {
printItem(10)
printItem("Hello")
}

代码解释

  • printItem<T> 是一个泛型函数,<T> 表示该函数使用了一个类型参数 T。在调用 printItem 函数时,Kotlin 会根据传入的参数自动推断 T 的具体类型。

在 Kotlin 中,当你使用泛型函数并希望访问传入对象的属性时,不能直接访问任意属性,因为编译器不知道泛型类型 T 具体是什么。为了解决这个问题,通常有以下几种方式:

泛型获取属性

使用泛型约束(推荐)

通过给泛型添加上界(upper bound),限定 T 必须是某个类或接口的子类型,这样就可以安全地访问该类/接口中定义的属性。

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 定义一个接口或开放类
interface Person {
val name: String
}

// 泛型函数,限定 T 必须实现 Person 接口
fun <T : Person> printName(item: T) {
println(item.name) // ✅ 可以安全访问 name
}

// 使用
data class Student(override val name: String, val grade: Int) : Person

fun main() {
val student = Student("Alice", 10)
printName(student) // 输出: Alice
}

Tip:

💡 你可以用 classinterface 或者 Any 作为上界,但只有上界中声明的成员才能被访问。

使用反射

不推荐用于常规逻辑

如果你无法控制传入类型的结构(比如完全通用的工具函数),可以使用 Kotlin 反射来动态获取属性。但这会带来性能开销,并且需要额外依赖 kotlin-reflect

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import kotlin.reflect.full.memberProperties

fun <reified T : Any> printProperty(obj: T, propertyName: String) {
val prop = T::class.memberProperties.find { it.name == propertyName }
prop?.let {
println("${propertyName} = ${it.get(obj)}")
}
}

data class User(val id: Int, val email: String)

fun main() {
val user = User(1, "alice@example.com")
printProperty(user, "email") // 输出: email = alice@example.com
}

注意:

反射应尽量避免在性能敏感或高频调用的代码中使用。

总结

方法 是否推荐 说明
泛型上界约束 ✅ 强烈推荐 类型安全、性能好、代码清晰
反射 ⚠️ 谨慎使用 灵活但慢,需额外依赖

泛型约束

泛型约束允许你限制类型参数必须是某个特定类型或其子类型,这样可以在泛型代码中使用特定类型的方法和属性。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 定义一个接口
interface Printable {
fun printInfo()
}

// 实现接口
class Person(val name: String) : Printable {
override fun printInfo() {
println("Person: $name")
}
}

// 泛型函数,使用泛型约束
fun <T : Printable> printAll(items: List<T>) {
for (item in items) {
item.printInfo()
}
}

fun main() {
val people = listOf(Person("Alice"), Person("Bob"))
printAll(people)
}

代码解释

  • Printable 是一个接口,Person 类实现了该接口。
  • printAll<T : Printable> 是一个泛型函数,使用了泛型约束 T : Printable,表示类型参数 T 必须是 Printable 接口的实现类。在函数内部,可以调用 T 类型对象的 printInfo 方法。

变型(协变、逆变和不变)

Kotlin 中的变型用于处理泛型类型之间的子类型关系,分为协变、逆变和不变三种情况。

协变(out

协变允许泛型类型作为生产者,只能从中读取数据,不能写入数据。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 协变泛型类
interface Producer<out T> {
fun produce(): T
}

class StringProducer : Producer<String> {
override fun produce(): String = "Hello"
}

fun main() {
val stringProducer: Producer<String> = StringProducer()
val anyProducer: Producer<Any> = stringProducer
println(anyProducer.produce())
}

代码解释

  • Producer<out T> 是一个协变泛型接口,使用 out 关键字表示协变。StringProducer 类实现了 Producer<String> 接口。
  • 由于协变的特性,Producer<String> 类型的对象可以赋值给 Producer<Any> 类型的变量。

逆变(in

逆变允许泛型类型作为消费者,只能向其中写入数据,不能读取数据。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 逆变泛型类
interface Consumer<in T> {
fun consume(item: T)
}

class AnyConsumer : Consumer<Any> {
override fun consume(item: Any) {
println("Consumed: $item")
}
}

fun main() {
val anyConsumer: Consumer<Any> = AnyConsumer()
val stringConsumer: Consumer<String> = anyConsumer
stringConsumer.consume("Hello")
}

代码解释

  • Consumer<in T> 是一个逆变泛型接口,使用 in 关键字表示逆变。AnyConsumer 类实现了 Consumer<Any> 接口。
  • 由于逆变的特性,Consumer<Any> 类型的对象可以赋值给 Consumer<String> 类型的变量。

不变

如果泛型类型既不是协变也不是逆变,则是不变的,即泛型类型之间不存在子类型关系。

星投影(*

星投影用于在不知道具体类型参数时使用泛型类型。

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
fun printElements(list: List<*>) {
for (element in list) {
println(element)
}
}

fun main() {
val intList = listOf(1, 2, 3)
val stringList = listOf("a", "b", "c")

printElements(intList)
printElements(stringList)
}

代码解释

  • List<*> 表示一个泛型列表,其中的元素类型未知。printElements 函数可以接受任何类型的列表作为参数。

通过以上介绍,你可以了解到 Kotlin 中泛型的基本概念和使用方法,包括泛型类、泛型接口、泛型函数、泛型约束、变型和星投影等。泛型可以提高代码的复用性和类型安全性,是 Kotlin 中非常重要的特性之一。