Kotlin范型摘要

2018-11-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 这种概念这么容易理解,可以在具体使用过程中细细体会。

星号投影
上一篇下一篇

猜你喜欢

热点阅读