# Kotlin教程 - 10 泛型
Kotlin 中的泛型允许将类型参数化,使代码更通用且可重用。这提高了代码的灵活性和可维护性。在 Kotlin 中,泛型常用于定义集合类和类似数据结构。
在 Kotlin 中使用容器时已经接触过泛型。例如:
fun main() {
// 不使用泛型,没有类型限制
val nameList1 = mutableListOf("Doubi", "Erbi", "Niubi", 111)
// 使用泛型进行类型限制,只能放字符串类型
val nameList2 = mutableListOf<String>("Doubi", "Erbi", "Niubi")
// nameList2.(123) // 报错,mutableListOf<String>中只能放String类型数据
}
2
3
4
5
6
7
8
同样,Map 也可以指定泛型:
fun main() {
// 不使用泛型,key和value可以是任意类型
val map1 = mapOf(123 to "one", "name" to "Doubi", "age" to 12)
// 使用泛型,key和value只能是字符串,否则报错
val map2 = mapOf<String, String>("name" to "Doubi", "age" to "12")
}
2
3
4
5
6
7
# 10.1 泛型类
在开发中,经常需要请求服务器获取各种信息,如学生信息或老师信息。我们会定义相关的业务类:
定义学生类:
data class Student(val name: String, val age: Int)
定义老师类:
data class Teacher(val name: String, val age: Int)
我们一般会定义一个返回的结果类,然后在类中封装返回码和返回的数据,通过这个结果类返回学生或老师的信息。
举个栗子:
data class DataResult(val messageCode: Int, val data: Any?)
因为我们返回的数据可能是不同的类型,所以我们将数据的类型定义为 Any
类型。因为有时候可能只需要返回信息码,没有数据信息,所以这里定义 data 可以为空,使用 Any?
定义。
测试一下:
fun main() {
// 封装数据
val stu = Student("Doubi", 12);
val result1 = DataResult(200, stu);
val tea = Teacher("Niubi", 32);
val result2 = DataResult(200, tea);
// 获取数据
if (result1.data is Student) {
val stu2 = result1.data as Student // 需要进行类型转换
print(stu2.name);
}
if (result2.data is Teacher) {
val tea2 = result2.data as Teacher // 需要进行类型转换
print(tea2.name);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
在上面的代码中,我们先将学生和老师的信息封装到了 DataResult
中。然后重新从 DataResult
获取老师和学生的信息。
但是获取到信息以后,需要进行类型判断,或这使用 as
进行类型转换,如果不进行类型判断,使用 val stu2 = result1.data
直接赋值代码会报错,因为 data
是 Any?
类型。
这个时候我们可以使用泛型,使用泛型我们可以编写更通用、可重用的代码,以适应不同类型的数据。
修改上面的代码:
class DataResult<T>(val messageCode: Int, val data: T?)
在类名后面添加 <T>
表示当前类声明的泛型,泛型可以任意字母,一般使用 T
,然后可以使用 T
声明变量 val data: T?
,如果需要用到多个泛型,也是可以定义多个的,比如: <T,E>
。
使用泛型后,测试一下:
fun main() {
val stu = Student("Doubi", 12)
val result1 = DataResult(200, stu)
val tea = Teacher("Niubi", 32)
val result2 = DataResult(200, tea)
val stu2: Student? = result1.data // 不需要进行类型转换
println(stu2?.name)
val tea2: Teacher? = result2.data // 不需要进行类型转换
println(tea2?.name)
}
2
3
4
5
6
7
8
9
10
11
12
13
这个在创建 DataResult
对象的时候,使用泛型,然后在通过对象来获取 data
的时候,就不用类型转换了。
# 10.2 泛型函数
泛型函数是一种具有泛型类型参数的函数,可以在函数签名中使用类型参数来实现更通用和灵活的代码。泛型函数可以在参数、返回类型或函数体中使用这些类型参数。
举个栗子:
我们写一个函数获取列表的最后一个元素:
// 获取列表的最后一个元素
fun <T> getLast(list: List<T>): T {
return list.last();
}
fun main() {
val intList = listOf(1, 2, 3)
val strList = listOf("Doubi", "Niubi", "Shabi")
val i: Int = getLast(intList); // 不用类型转换
println(i);
val s: String = getLast(strList) // 不用类型转换
println(s);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
在上面的代码中定义了一个泛型方法 fun <T> getLast(list: List<T>): T
,fun
后面的 <T>
定义函数中用到的泛型类型,最后面的 T
表示返回值的类型。
通过使用泛型定义,我们不同数据类型的列表都可以调用,而且在获取到元素后,不要类型转换。
# 10.3 泛型接口
在 Kotlin 中,泛型接口是指可以具有泛型类型参数的接口。泛型接口允许在接口定义中使用类型参数,并在实现接口时指定具体的类型参数。
举个栗子:
定义一个泛型接口。
// 定义一个泛型接口
interface Repository<T> {
fun save(data: T)
fun findById(id: Int): T
}
2
3
4
5
下面写一个类来实现这个泛型接口:
// 实现泛型接口
class UserRepository : Repository<String> {
override fun save(data: String) {
println("Saving user: $data")
}
override fun findById(id: Int): String {
return "User with ID $id"
}
}
2
3
4
5
6
7
8
9
10
我们在实现接口的时候,指定了泛型的类型,那么类中用到泛型的地方,类型就确定确定了。
我们还可以定义其他的子类来实现该接口,并使用不同的数据类型。
调用:
fun main() {
val userRepository = UserRepository()
userRepository.save("Doubi")
val user = userRepository.findById(1)
println(user)
}
2
3
4
5
6
# 10.4 泛型约束
Kotlin 中的泛型约束允许你限制泛型类型参数可以是哪种类型,这有助于提高代码的安全性和可靠性。通过泛型约束,你可以指定泛型类型参数必须符合特定的条件,例如必须是某个类的子类、必须实现某个接口,或者必须具有某些属性。
# 1 类和接口约束
我们可以在泛型类中约束泛型必须是某个类的子类。
举个栗子:
interface Engine {
fun getName(): String
}
class Car : Engine {
override fun getName(): String {
return "小汽车"
}
}
class Bike
class Garage<T : Engine> {
// 加油
fun putGas(vehicle: T) {
println("正在给 ${vehicle.getName()} 加油")
}
}
fun main() {
val carGarage = Garage<Car>()
val bikeGarage = Garage<Bike>() // 报错 Bike不属于Engine接口的实现
val car = Car()
carGarage.putGas(car) // 正在给 小汽车 加油
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
在上面的代码中,Garage<T : Engine>
限制了泛型 T
必须实现 Engine
接口,所以在下面的代码中 Garage<Bike>
会报错。
再举个例子:
open class Animal(val name: String, val age: Int)
class Cat(name: String, age: Int) : Animal(name, age)
// 限制T 必须是 Animal 类的子类
fun <T : Animal> printInfo(item: T) {
println("Name: ${item.name}, Age: ${item.age}")
}
fun main() {
val cat = Cat("Doubi", 5)
printInfo(cat) // 可以正常调用,因为 Cat 是 Animal 的子类
}
2
3
4
5
6
7
8
9
10
11
12
13
在上面通过 <T : Animal>
限制 T 必须是 Animal类的子类。
# 2 子类和接口同时约束
我们在进行泛型约束的时候,也可以同时约束泛型类型参数实现某个父类和某个接口。可以使用 where
关键字在泛型参数声明时使用逗号 ,
分隔不同的约束条件。
举个栗子:
// 定义一个接口
interface Helpful {
fun help()
}
open class Animal
class Dog : Animal(), Helpful { // 类实现接口
override fun help() {
println("我会看门")
}
}
fun <T> performSound(animal: T) where T : Animal, T : Helpful { // 同时约束父类和接口
animal.help()
}
fun main() {
val dog = Dog()
performSound(dog)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
在上面的代码中,使用 where T : Animal, T : Helpful
同时约束泛型的父类和接口。
# 10.5 协变与逆变
# 1 协变
什么是协变?
协变允许泛型类型参数在子类型化关系中保持一致,即使泛型类型参数发生向上转换(父类关系),你仍然可以将泛型类型视为相同类型。
什么意思呢?
先举个栗子:
interface Animal {
fun sound()
}
class Dog : Animal {
override fun sound() {
println("汪汪汪!")
}
}
class Cat : Animal {
override fun sound() {
println("喵喵喵!")
}
}
class Producer<out T>(private val value: T) {
fun produce(): T { // 只能作为方法的返回值
return value
}
}
fun main() {
val dogProducer: Producer<Dog> = Producer(Dog())
val catProducer: Producer<Cat> = Producer(Cat())
val dog: Dog = dogProducer.produce()
val cat: Cat = catProducer.produce()
dog.sound() // 汪汪汪!
cat.sound() // 喵喵喵!
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
在上面的 Producer 类中,使用了协变,所以泛型只能用于类中的方法返回值,不能用于方法参数。
注意:协变表示泛型只能用作为输出,不能作为输入,也就是只能作为方法返回值,不能作为方法输入的参数。协变使用 out
关键字声明泛型类型。
# 2 逆变
什么是逆变?
逆变表示泛型只能用作为输入,不能作为输出,也就是只能作为方法参数,不能作为方法返回值。协变使用 in
关键字声明泛型类型。
举个栗子:
interface Animal {
fun sound()
}
class Dog : Animal {
override fun sound() {
println("汪汪汪!")
}
}
class Cat : Animal {
override fun sound() {
println("喵喵喵!")
}
}
class Consumer<in T:Animal> {
fun consume(value: T) { // 只能作为方法参数
value.sound()
}
}
fun main() {
val animalConsumer: Consumer<Animal> = Consumer()
val dog: Dog = Dog()
val cat: Cat = Cat()
animalConsumer.consume(dog) // 汪汪汪!
animalConsumer.consume(cat) // 喵喵喵!
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
在上面的 Consumer 类中,使用了逆变,所以泛型只能用于类中的方法参数,不能作为返回值。
# 10.6 reified
先看一段代码:
fun <T> checkType(value: Any) {
if (value is T) { // 报错
println("$value is of type ${T::class.simpleName}") // 报错
} else {
println("$value is not of type ${T::class.simpleName}")
}
}
fun main() {
checkType<String>("Hello") // 输出:Hello is of type String
checkType<Int>("Hello") // 输出:Hello is of type String
}
2
3
4
5
6
7
8
9
10
11
12
上面的代码 value is T
是报错的,因为在函数运行时,泛型类型的信息被擦除,因此无法直接访问。
所以如果想在运行的时候获取类型信息,可以使用 reified 关键字。
举个栗子:
inline fun <reified T> checkType(value: Any) {
if (value is T) { // 可以获取到类型信息
println("$value is of type ${T::class.simpleName}")
} else {
println("$value is not of type ${T::class.simpleName}")
}
}
fun main() {
checkType<String>("Hello") // 输出:Hello is of type String
checkType<String>(10) // 输出:10 is not of type String
}
2
3
4
5
6
7
8
9
10
11
12
reified
是一个关键字,主要与内联函数一起使用。它允许在内联函数中获取泛型类型的实际类。