美文网首页
Kotlin 泛型配合实例解析(out in 星投影。。。有点头

Kotlin 泛型配合实例解析(out in 星投影。。。有点头

作者: 九风特 | 来源:发表于2022-01-06 15:32 被阅读0次

    开场白
    Kotlin是个不错的语言,但也有负面消息。我对它唯一的负面印象是有些语法太简单和抽象以至于不会kotlin的人会很懵逼。你可能会说不会当然懵逼了,但是你去看c++,java这些高级语言,即便不会只要有编程基础都能看个大概。 总之我认为kotlin就是一个精干的语言。泛型在kotlin家族里算是一个稍微抽象的东西了,有时候你很可能想简单会用即可,在实际编程中也可能确实如此,但当你想深入了解一些kotlin源码的时候,你就会发现它大量使用了泛型的特性和语法,所以还是很有必要更深入的了解清楚的好。

    官网文档
    大多数时候学习一个东西最好的地方就是官方文档
    但很多时候可能看完了并不太明白,还需要一些自己总结和实验,本文就是这种总结和实验,所以我假设你已经看完了这个官方文档了

    最普通的泛型
    最普通的泛型应用几乎没什么可说的,这在众多高级语言里都有这种东西,为了全面我们还是说一下

    class MyArray<T>
    

    这就是一个最简的kotlin泛型类了,我们把它丰富一下,让程序能用它输出点什么

    class MyArray<T>{
        val arr = ArrayList<T>()
        operator fun get(index:Int):T{
            return arr[index]
        }
        operator fun set(index: Int, newValue:T){
            arr[index] = newValue
        }
    }
    

    这代码几乎没什么好说的,就是加了一个类变量arr,以及相当于重载了[]和=号的类方法, 在main函数用用他

        val myArray = MyArray<Int>()
        myArray.arr.addAll(listOf(1, 2, 3))
        println(myArray[2])//输出 3
    

    很简单吧,一切如预期。

    out in 出场
    我本来想逐个结合实例来说明out 和 in但感觉没这个必要,这俩关键字看官方文档足够了咱们只做个最终总结,然后稍微详细的说说星投影
    out T<==>T可以是T或者T的子类 相当于java的?extends
    int T<==>T可以是T或T的父类 相当于java的?super
    我觉得过多的语言反而会让人迷惑,比如消费者 生产者什么的,记住以上两条即可
    至于官文提到的使用处形变其实原理没变,只是out in出现的位置不同而已

    星投影
    看着挺吓人的是吧,别以为这个是个类似星星错综复杂就是了,这个“星”纯粹就是字面意思的“*”号
    关于这个我们先来看看官网文档原文:

    Star-projections

    Sometimes you want to say that you know nothing about the type argument, but you still want to use it in a safe way. The safe way here is to define such a projection of the generic type, that every concrete instantiation of that generic type will be a subtype of that projection.

    Kotlin provides so-called star-projection syntax for this:

    • For Foo<out T : TUpper>, where T is a covariant type parameter with the upper bound TUpper, Foo<*> is equivalent to Foo<out TUpper>. This means that when the T is unknown you can safely read values of TUpper from Foo<*>.

    • For Foo<in T>, where T is a contravariant type parameter, Foo<*> is equivalent to Foo<in Nothing>. This means there is nothing you can write to Foo<*> in a safe way when T is unknown.

    • For Foo<T : TUpper>, where T is an invariant type parameter with the upper bound TUpper, Foo<*> is equivalent to Foo<out TUpper> for reading values and to Foo<in Nothing> for writing values.

    If a generic type has several type parameters, each of them can be projected independently. For example, if the type is declared as interface Function<in T, out U> you could use the following star-projections:

    • Function<*, String> means Function<in Nothing, String>.

    • Function<Int, *> means Function<Int, out Any?>.

    • Function<*, *> means Function<in Nothing, out Any?>.

    我们看到它主要说了3条,我们来一条一条的看看
    第一条:

    • For Foo<out T : TUpper>, where T is a covariant type parameter with the upper bound TUpper, Foo<*> is equivalent to Foo<out TUpper>. This means that when the T is unknown you can safely read values of TUpper from Foo<*>.

    对于 Foo <out T : TUpper>,其中 T 是一个具有上界 TUpper 的协变类型参数,Foo <*> 等价于 Foo <out TUpper>。 这意味着当 T 未知时,你可以安全地从 Foo <*> 读取 TUpper 的值。
    为了演示它我们先来定义三个很简单的类

    open class Animal(val name:String)
    open class Person(name:String):Animal(name)
    class Worker(name:String):Person(name)
    

    然后按照条目中的说法在定义一个Star1类拥有泛型Foo <out T : TUpper>的样式 如下

    class Star1<out T:Animal>(val u:T){
    }
    

    那么我们现在编写一个简单的函数来试用一下条目1的星投影

    fun printStar1(star1: Star1<*>){
        println(star1.u.name)
    }
    

    Star1<*>的类型定义就是星投影了,按照条目的文字这个函数定义等价于

    fun  printNormal1(star1:Star1<out Animal>){
        println(star1.u.name)
    }
    

    我们实际调用下试试看就行了

        val anim = Animal("Animal")
        val person = Person("Person")
        val worker = Worker("Worker")
    
        val starAnim1 = Star1(anim)
        val starPerson1 = Star1(person)
        val starWorker1 = Star1(worker)
        printStar1(starAnim1)
        printStar1(starPerson1)
        printStar1(starWorker1)
    

    看看输出一切正常

    条目2

    • For Foo<in T>, where T is a contravariant type parameter, Foo<*> is equivalent to Foo<in Nothing>. This means there is nothing you can write to Foo<*> in a safe way when T is unknown.
      这一条没啥说的似乎 直接看看测试代码即可了
    class Star2<in T>{
        operator fun compareTo(other: T): Int = 0
    }
    fun printStart2(x:Star2<*>){
        x.compareTo(1)//编译不过,因为需要nothing类型的参数,言外之意是你传什么都不对
    }
    fun printNormal2(x:Star2<in Nothing>){
        x.compareTo(1)//编译不过,因为需要nothing类型的参数,言外之意是你传什么都不对
    }
    

    条目3

    • For Foo<T : TUpper>, where T is an invariant type parameter with the upper bound TUpper, Foo<*> is equivalent to Foo<out TUpper> for reading values and to Foo<in Nothing> for writing values.
      这个自己试试啊,其实就是相当于读的时候相当于第一条,写的时候相当于第二条
      在看看底下的各种映射,其实无非都是在说明 <*>代表什么
      除了上面说的 还有一个 foo<T>的话 foo<*>就代表 foo<out any?>

    说了半天星投影,你可能会说,啥乱七八糟的,这玩意有毛用啊。 好我们来看一个需求,我们要给Array写一个扩展函数,这个函数的目的是分析Array中元素的类型,用指定类型的元素形成一个新的Array并返回它,这个怎么搞呢?
    我们来看这段代码

        val arr = arrayOf<Any>(1, 1.1, 2, 2.2, 3, 3.3)
        arr.map { print("$it ") }
        val arr2 = arr.myArrFilter<Double>()
        println(arr2)
    

    我们想通过myArrFilter这个函数挑出所有的double类型的数,形成一个新的数组arr2
    [1.1, 2.2,3.3]
    那怎么实现这个myArrFilter扩展函数呢?这个显然是对各种类型都可以用的你不能这样
    Array<Double>.myArrFilter = .... 这样只对Double类型的array能用,那怎么模糊化它呢,那就是我们*了下面是具体代码

    inline fun <reified R> Array<*>.myArrFilter(): List<R> {
        val destination = ArrayList<R>()
        for (element in this) if (element is R) destination.add(element)
        return destination
    }
    

    你加了这个扩展后,程序按照预期输出了,但实际上这是Array已经提供了的功能,你可以自己看看它的原始实现,我贴一下,结束本篇讨论吧

    public inline fun <reified R> Array<*>.filterIsInstance(): List<@kotlin.internal.NoInfer R> {
        return filterIsInstanceTo(ArrayList<R>())
    }
    public inline fun <reified R, C : MutableCollection<in R>> Array<*>.filterIsInstanceTo(destination: C): C {
        for (element in this) if (element is R) destination.add(element)
        return destination
    }
    

    相关文章

      网友评论

          本文标题:Kotlin 泛型配合实例解析(out in 星投影。。。有点头

          本文链接:https://www.haomeiwen.com/subject/dlolcrtx.html