Kotlin 泛型

Kotlin 泛型

概述

  • 泛型是对程序的一种抽象,可以借助泛型对代码的复用。

  • 支持类型检查,能在编译期检查出问题。

  • 还可以在定义泛型时,设置边界限制。

  • 从型变的位置来分类的话,分为使用处型变和声明处型变。

  • 从型变的父子关系来分类的话,分为逆变和协变。逆变表示父子关系颠倒了,而协变表示父子关系和原来一致。

  • 型变的口诀:泛型作为参数,用 in;泛型作为返回值,用 out。在特殊场景下,同时作为参数和返回值的泛型参数,我们可以用 @UnsafeVariance 来解决型变冲突。

简单使用

通过泛型定义 Controller 控制类,泛型 T 代表传入类型。

open class Animal {
    override fun toString(): String {
        return "Animal"
    }
}

class Dog : Animal() {
    override fun toString(): String {
        return "Dog"
    }
}

class Cat : Animal() {
    override fun toString(): String {
        return "Cat"
    }
}

class Controller<T> {
    fun showAnimal(t: T) {
        println(t.toString())
    }
}

fun main() {
    val controller = Controller<Animal>()
    controller.showAnimal(Animal())
    controller.showAnimal(Dog())
    controller.showAnimal(Cat())
}

说明:方法 showAnimal 可以接受不同类型的对象,并依次打印出内容。

泛型函数

在 Kotlin 中函数是一等公民。

fun <T> showAnimal(t: T) {
    println(t.toString())
}

fun main() {
    showAnimal(Animal())
    showAnimal(Dog())
    showAnimal(Cat())
}

说明:效果与泛型类一样。

泛型边界

  • 在 Java 中可以通过 <? extends Base> 指定上界是 Base 类型,表示前者是后者的子类。
  • 在 Kotlin 中可以通过 T : Base 指定上界是Base类型。
class Apple {
    override fun toString(): String {
        return "Apple"
    }
}

fun <T:Animal> showAnimal(t: T) {
    println(t.toString())
}

fun main() {
    showAnimal(Animal())
    showAnimal(Dog())
    showAnimal(Cat())
    // showAnimal(Apple()) // 提示错误
}

限制多个类型

可以通过 where 关键字实现。

// 吃鱼接口
interface IFish {}

// 吃骨头接口
interface IBone {}

class Dog : Animal(), IBone {
    override fun toString(): String {
        return "Dog"
    }
}

class Cat : Animal(), IFish {
    override fun toString(): String {
        return "Cat"
    }
}

fun <T> showEatFish(t: T) where T : Animal, T : IFish {
    println(t.toString())
}

fun main() {
    showEatFish(Cat())
    // showEatFish(Animal()) // 提示错误
    // showEatFish(Dog()) // 提示错误  
}

型变

  • 型变是为了解决泛型的不变性问题。
  • 根据位置分类:
    • 使用处型变
    • 声明处型变
  • 根据父子关系分类:
    • 逆变:负责关系颠倒
    • 协变:父子关系不变

不变性问题

虽然 Cat 是 Animal 的子类,但是 MutableList<Cat>MutableList<Animal> 不存在任何关系(包括继承关系),这就是泛型的不变性。

fun showAnimalList(list: MutableList<Animal>) {
    list.forEach {
        println(it.toString())
    }
}

fun main() {
    val animalList = mutableListOf<Animal>(Animal())
    showAnimalList(animalList)

    val catList = mutableListOf<Cat>(Cat())
    // showAnimalList(catList) // 报错
}

说明:

  • showAnimalList() 函数只能接收 MutableList<Animal> 类型的集合,如果传入 MutableList<Animal> 类型就会报错,提示类型不匹配。
  • 虽然 Animal 与 Cat 存在父子关系,但是 MutableList<Animal>MutableList<Cat> 没有任何关系,因此传入不同类型的对象会报错,这就是泛型的不变性。

协变 out

协变表示输出、生产、获取,使用 out 关键字。

Kotlin 中的 <out T> 类似于 Java 中的 <? extends T>

协变问题
fun getAnimal(list: MutableList<Animal>): Animal {
    return if (list.size > 0) {
        list[0]
    } else {
        Animal()
    }
}

fun main() {
    val animalList = mutableListOf<Animal>(Animal())
    getAnimal(animalList)

    val catList = mutableListOf<Cat>(Cat())
    // getAnimal(catList) // 报错,提示类型不匹配
}

说明:getAnimal() 函数要求传入 MutableList<Animal> 类型,而实际传入 MutableList<Cat> 类型,因此报错提示类型不匹配。

解决方式一:使用处协变

在 getAnimal() 函数的参数的泛型类型前面添加 out 关键字。

这样代码就可以通过编译了,可以看作将 MutableList<Cat> 看作为 MutableList<Animal> 的子类。这称为”泛型的协变“。

//                            使用处协变
//                               ↓
fun getAnimal(list: MutableList<out Animal>): Animal {
    return if (list.size > 0) {
        list[0]
    } else {
        Animal()
    }
}
解决方式二:声明处协变

在 FruitShop 类的泛型参数前面添加 out 关键字

//                                  声明处协变
//                                    ↓
class AnimalProducer<T : MutableList<out Animal>>(private val animalList: T) {

    fun getAnimal(): Animal {
        return if (animalList.size > 0) {
            animalList[0]
        } else {
            Animal()
        }
    }
}
// 使用:
val catList = mutableListOf<Cat>(Cat())
val animalProducer = AnimalProducer(catList)
animalProducer.getAnimal()

逆变 in

逆变表示输入、消费、传递,使用 in 关键字。

Kotlin 中的 <in T> 类似于 Java 中的 <? super T>

逆变问题
fun deleteAnimal(list: MutableList<Cat>) {
    if (list.size > 0)
    	list.removeAt(0)
}

fun main() {
    val catList = mutableListOf<Cat>(Cat())
    deleteAnimal(catList)

    val deleteAnimal = mutableListOf<Animal>(Animal())
    // setAnimal(animalList) // 报错,提示类型不匹配
}

说明:提示类型不匹配。

解决方式一:使用处逆变

在 deleteAnimal() 函数的参数中的泛型类型前面添加 in 关键字。

这样代码就可以通过编译了,可以将 MutableList<Animal> 看作为 MutableList<Cat> 的子类。这称为”泛型的逆变“。

//                               使用处逆变
//                                  ↓
fun deleteAnimal(list: MutableList<in Cat>) {
    if (list.size > 0)
        list.removeAt(0)
}
解决方式二:声明处逆变

在 AnimalConsumer 类的泛型参数前面添加 in关键字

//                               声明处逆变
//                                   ↓
class AnimalConsumer<T : MutableList<in Cat>>(private val catList: T) {

    fun consume() {
        if (catList.size > 0)
            catList.removeAt(0)
    }
}
// 使用:
val animalList = mutableListOf<Animal>(Animal())
val consumer = AnimalConsumer(animalList)
consumer.consume()

数组拷贝

fun <T> copyIn(src: Array<T>, dest: Array<in T>) {
    if (dest.size < src.size) {
        throw IndexOutOfBoundsException()
    } else {
        src.forEachIndexed { index, value ->
            dest[index] = src[index]
        }
    }
}

fun <T> copyOut(src: Array<out T>, dest: Array<T>) {
    if (dest.size < src.size) {
        throw IndexOutOfBoundsException()
    } else {
        src.forEachIndexed { index, value ->
            dest[index] = src[index]
        }
    }
}

星投影

Kotlin 中的星投影指用 * 作为泛型的实参,表示接收任意类型。

相当于 out Any

fun getAnimalList(type: Int): MutableList<*> {
    if (type == 1) {
        return mutableListOf<Animal>(Animal())
    } else {
        return mutableListOf<Cat>(Cat())
    }
}

fun main() {
    val animalList = getAnimalList(1)
    val catList = getAnimalList(0)     
}

等价于:

fun getAnimalList(type: Int): MutableList<out Any> {
    if (type == 1) {
        return mutableListOf<Animal>(Animal())
    } else {
        return mutableListOf<Cat>(Cat())
    }
}

型变总结

在这里插入图片描述

Consumer in, Producer out !大概意思:消费者 in,生产者 out。

  • 协变情况:使用 out 关键字,是一种读取行为。泛型作为返回值时。
  • 逆变情况:使用 in 关键字,是一种输入行为。泛型作为参数时。
  • 特殊场景:同时作为参数和返回值的泛型参数,可以用 @UnsafeVariance 解决冲突。
KotlinJava边界
协变List<out TextView>List<? extends TextView>上限
逆变List<in TextView>List<? super TextView>下限

特殊情况

同时存在out in,官方案例

//                   协变    
//                    ↓      
public interface List<out E> : Collection<E> {
//                                泛型作为返回值
//                                       ↓    
    public operator fun get(index: Int): E
//                                           泛型作为参数
//                                                 ↓    
    override fun contains(element: @UnsafeVariance E): Boolean
//                                        泛型作为参数
//                                              ↓   
    public fun indexOf(element: @UnsafeVariance E): Int
}

获取泛型参数的类型

可以借助匿名内部类实现,泛型擦除并不是真的将全部的类型信息擦除,而是将类型信息存放在对于class的常量池中。

open class Controller<T> {
}

fun main() {
    val controller = object : Controller<Cat>() {}
    val superClass = controller.javaClass.genericSuperclass
    println(superClass) //com.example.lib_kt.Controller<com.example.lib_kt.Cat>
    val type = (superClass as ParameterizedType).actualTypeArguments[0]
    println(type) //class com.example.lib_kt.Cat
}

具体化类型参数 reified

在Java中泛型本质都是Object,而在Kotlin中可以通过关键字reified具体化类型参数

inline fun <reified T : Activity> Activity.startActivity(context: Context) {
    startActivity(Intent(context, T::class.java))
}

//使用:
startActivity<TwoActivity>(context)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值