Kotlin进阶 - 泛型

在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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 建议先阅读我的上一篇文章 -- Java 泛型 和 Java 泛型一样,Kotlin 泛型也是 Kotlin 语言...
    JohnnyShieh阅读 6,613评论 1 26
  • Kotlin 泛型 声明泛型类 和Java一样,Kotlin通过在类名称后面加上一对尖括号,并把类型参数放在尖括号...
    leilifengxingmw阅读 2,617评论 0 0
  • 在日常编程中, 我们经常会用到泛型, 用的时候感觉并不复杂, 然而最近在做Kotlin开发时, 被其中的逆变和协变...
    AssIstne阅读 1,122评论 0 0
  • 泛型产生 Java泛型是JDK1.5引入的一个新特性,是一种参数化类型。参数化类型就是在不创建新类型的情况下,通过...
    kevinsEegets阅读 770评论 3 1
  • 1、泛型的实化 Java中泛型是在JDK1.5引入的,是一个伪泛型,它是通过泛型擦除机制来实现的。泛型只存在编译时...
    code希必地阅读 1,093评论 0 0

友情链接更多精彩内容