在Android开发中我们经常用到泛型,如:List、Map、Set、Adapter等,那么在Kotlin中同样支持泛型。
什么是泛型呢?
泛型:把类型明确的工作推迟到创建对象或调用方法的时候才去明确的特殊的类型。
一、Kotlin中定义泛型方式
在Kotlin中定义泛型有跟Java一样,有以下两种方式:
- 定义在类上
- 定义在函数中
定义在类上示例:
class MagicBox<T>(val item: T) {
var available = false
fun fetch(): T? {
return item.takeIf { available }
}
}
定义在函数中
class MagicBox<T>(val item: T) {
var available = false
/**
* 函数中增加范式类型,类似Java
*/
fun <R> fetch(anotherGenericity: (T) -> R): R? {
return anotherGenericity(item).takeIf { available }
}
}
二、Kotlin中定义约束泛型
Kotlin中定义约束泛型与Java < T extend XXX>定义泛型的方式类似,表示泛型指定的类型必须为指定类型的类或继承了指定类型类的子类。这句话有点绕,举个例子:定义一泛型class A<T : B>(),传入的泛型T必须为B或者B的子类。完整示例如下:
//定义一个约束泛型
private class ConstraintMagicBox<T:Human>(item:T){
}
//定义一个父类
private open class Human(val name: String,val age:Int)
//定义一个Human类的子类
private class Male(name: String,age: Int):Human(name, age)
//定义一个Human类的子类
private class Female(name: String,age: Int):Human(name, age)
fun main() {
//同为Human类型的可传入
val male = ConstraintMagicBox(Male("Jack", 20))
val female = ConstraintMagicBox(Female("Jim", 20))
}
三、Kotlin定义数量可变参数泛型
这点类似Java中给方法定义可变参数(private void method(int.. num)),在Kotlin中定义方式要借助关键字vararg,普通函数和构造函数均可使用。
示例如下:
private class VarMagicBox<T : VarHuman>(vararg val items: T) {
//根据参数从items中获取数据,其中items类型未Array
fun fetch(index: Int): T? {
return items.getOrNull(0)
}
//在函数中增加可变参数
fun fetch(vararg indexs: Int):List<T>{
indexs.takeIf {
indexs.isNotEmpty()
}.run {
return items.filterIndexed { index, t ->
indexs.contains(index)
}
}
}
}
private open class VarHuman(val name: String, val age: Int)
四、Kotlin中用in、out修饰泛型
在Java中在定义一个List中,指定一个特定类型,创建实例时只能new该类型的实例,无法new出子类或者父类的实例,
例子:
ArrayList<String> list = new ArrayList<CharSequence>()
或者
ArrayList<CharSequence> list = new ArrayList<String>()
这两种写法在JAVA是不支持的,正确的写法为:ArrayList<String> list = new ArrayList<String>()。
但是在Kotlin中可以做到支持。
当然针对上面定义的泛型例子同样不支持,那么Kotlin如何才能支持这样的写法呢?
因为Kotlin中有两个重要的关键字in(逆变)、out(协变)
-
out(协变):关键字用于定义协变(Covariance)。它意味着一个泛型类型参数可以是指定类型或者它的子类型。也就是说,对于类型A和B,如果A是B的子类型,那么Producer<A>是Producer<B>的子类型。这里的Producer是一个带有out关键字修饰泛型参数的类型,像接口或者类。用于函数返回值类型 -
in(逆变):关键字主要用于定义逆变(Contravariance)。它表示一个泛型类型参数可以是指定类型或者它的超类型。简单来说,就是对于类型A和B,如果A是B的子类型,那么Consumer<B>是Consumer<A>的子类型。这里的Consumer是一个具有in关键字修饰泛型参数的类型,例如接口或者类。用于函数参数类型
用法非常简单,先上示例,看下使用方式:
//out修饰的泛型 仅将泛型作为函数返回值
//作用:让子类泛型对象可以赋值给父类泛型对象
interface OutTest<out T>{
fun outTest():T
}
//in修饰的泛型 仅将泛型作为函数参数,泛型无法当做返回值
//作用:让父类泛型对象可以赋值给子类泛型对象
interface InTest<in T>{
fun inTest(param : T)
}
测试代码如下:
open class Food()
open class FastFood():Food()
class Hamburg():FastFood()
class FastFoodStore() : OutTest<FastFood>{
override fun outTest(): FastFood {
println("FastFoodStore ----------")
return FastFood()
}
}
class HamburgStore():InTest<FastFood>{
override fun inTest(param: FastFood) {
println("HamburgStore-----------")
}
}
fun main() {
//子类对象可以传给父类泛型对象 out
val food1 : OutTest<Food> = FastFoodStore()
//父类对象可以传给子类泛型对象 in
val food2 : InTest<Hamburg> = HamburgStore()
}
关键字in、out使用总结
主要有两点:
out修饰的泛型只能在函数返回值中使用,in修饰的泛型只能在函数的参数中使用;-
out修饰的泛型只能将子类泛型对象赋值给父类泛型对象,in修饰的泛型只能将父类泛型对象赋值给子类泛型对象,如下图示;
image.png
| 比较项目 | in关键字 | out关键字 |
|---|---|---|
| 概念含义 | 表示泛型类型参数可以是指定类型或者它的超类型,体现逆变特性。如果类型A是类型B的子类型,那么Consumer<B>是Consumer<A>的子类型 |
表示泛型类型参数可以是指定类型或者它的子类型,体现协变特性。如果类型A是类型B的子类型,那么Producer<A>是Producer<B>的子类型 |
| 使用场景 | 主要用于函数参数类型,定义能接收多种相关类型(超类型方向)作为参数的函数或接口。如interface AnimalHandler<in T> { fun handle(animal: T): Unit }
|
主要用于函数返回值类型,定义能返回多种相关类型(子类型方向)的函数或接口。如interface Producer<out T> { fun produce(): T }
|
| 位置限制 | 不能用于返回值类型,若在返回值位置使用会导致编译错误 | 不能用于函数参数类型,若在函数参数位置使用会导致编译错误 |
| 目的和效果 | 实现更灵活的函数参数类型匹配,让更具体类型(子类型)的实现可用于更通用类型(超类型)的消费者,增强代码通用性和灵活性 | 在处理返回值类型时利用协变特性,使子类型生产者可当作超类型生产者使用,方便类型转换和赋值,保证类型安全,构建具有多态性返回值类型的接口和类更方便 |
欢迎留言大家互相交流学习!
示例源码地址kotlin_demo
