美文网首页
Kotlin范型摘要

Kotlin范型摘要

作者: 蒋扬海 | 来源:发表于2018-11-12 13:12 被阅读0次

    范型类型参数

    声明范型类型

    interface List<T> { // List 接口定义了类型参数 T
        operator fun get(index: Int): T // 在接口或类的内部,T 可以当作普通类型使用
        // ...
    }
    

    你还可以用同样的语法声明泛型的扩展属性。

    val <T> List<T>.penultimate: T  // 这个泛型扩展属性能在任何种类元素的列表上调用
        get() = this[size - 2]
    >>> println(listOf(1, 2, 3, 4).penultimate) // 在这次调用中,类型参数 T 被推导成 Int
    3
    

    可以给类或接口的方法、顶层函数,以及扩展函数声明类型参数。

    范型约束

    上界约束

    fun <T : Number> oneHalf(value: T): Double { // 指定 Number 为类型形参的上界
        return value.toDouble() / 2.0 // 调用 Number 类中的方法
    }
    

    制定多个上界(使用 where

    fun <T> ensureTrailingPeriod(seq: T) where T : CharSequence, T : Appendable { // 类型参数约束的列表
        if (!seq.endsWith('.')) { // 调用为 CharSequence 接口定义的扩展函数
            seq.append('.') // 调用 Appendable 接口的方法
        }
    }
    

    事实上,没有指定上界的类型形参将会使用Any?这个默认的上界。

    class Processor<T> {
        fun process(value: T) {
            value?.hashCode() // “value”是可空的,所以要用安全调用
        }
    }
    

    非空上界

    class Processor<T : Any> { // 指定非“空”上界
        fun process(value: T) {
            value.hashCode() // 类型 T 的值现在是非“空”的
        }
    }
    

    类型擦除和实化类型参数

    概念:

    和 Java 一样,Kotlin 的泛型在运行时也被擦除了。这意味着泛型类实例不会携带用于创建它的类型实参的信息。例如,如果你创建了一个List<String>并将一堆字符串放到里面,在运行时你只能看到它是一个List。不能识别出列表本打算包含的是哪种类型的元素。

    在调用泛型函数的时候,在函数体中你不能决定调用它用的类型实参:

    >>> fun <T> isA(value: Any) = value is T
    Error: Cannot check for instance of erased type: T
    

    通常情况下都是这样,只有一种例外可以避免这种限制:内联函数。

    如果你把前面例子中的isA函数声明成inline并且用reified标记类型参数,你就能够用该函数检查value是不是T的实例。

    inline fun <reified T> isA(value: Any) = value is T // 现在代码可以编译了
    
    >>> println(isA<String>("abc"))
    true
    >>> println(isA<String>(123))
    false
    

    另一个例子

    val serviceImpl = ServiceLoader.load(Service::class.java)
    

    现在让我们用带实化类型参数的函数重写这个例子:

    val serviceImpl = loadService<Service>()
    

    我们看看这个loadService函数是如何定义的:

    inline fun <reified T> loadService() { // 类型参数标记成了“reified”
        return ServiceLoader.load(T::class.java) // 把 T::class 当成类型形参的类访问
    }
    

    Android 中的使用

    inline fun <reified T : Activity>
            Context.startActivity() { // 类型参数标记成了“reified”
        val intent = Intent(this, T::class.java) // 把 T::class 当成类型参数的类访问
        startActivity(intent)
    }
    
    startActivity<DetailActivity>() // 调用方法显示 Activity
    

    变型:协变与逆变

    Java 中要声明一个协变的泛型类型,你只需要:

    `List<Dog> dogs = new ArrayList<>();List<? extends Animal> animals = dogs;`
    

    extends关键字将动物列表与狗狗列表之间的关系声明为了协变

    需要逆变的时候,只需:

    `Compare<Animal> animalCompare = (first, second) -> first.getSize() - second.getSize();Compare<? super Spider> spiderCompare = animalCompare;`
    

    super关键字将动物比较器与蜘蛛比较器之间的关系声明为了逆变

    Kotlin 中,要声明类在某个类型参数上是可以协变的,在该类型参数的名称前加上out关键字就可以:

    interface Producer<out T> { // 类被声明成在 T 上协变。
        fun produce(): T
    }
    

    而逆变则是通过 in 关键字:

    interface Comparator<in T>  {
        fun compare(e1: T, e2: T): Int { ... } // 在“in”位置使用 T。
    }
    

    类的类型参数前的out关键字要求所有使用T的方法只能把T放在out位置而不能放在in位置。

    一个类可以在一个类型参数上协变同时在另外一个类型参数上逆变。

    interface Function1<in P, out R> {
        operator fun invoke(p: P): R
    }
    

    型变是比较重要但不是像 1 + 1 = 2 这种概念这么容易理解,可以在具体使用过程中细细体会。

    星号投影
    • kt 中 <*> 等价于 java 中的 <?>
    • 星号投影不能写入,只能读取
    • 与 Any? 是不同的: List<*> 中的元素只能是一种数据类型,但 List<Any?> 的元素可以是任意类型。

    相关文章

      网友评论

          本文标题:Kotlin范型摘要

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