范型类型参数
声明范型类型
interface List { // List 接口定义了类型参数 T
operator fun get(index: Int): T // 在接口或类的内部,T 可以当作普通类型使用
// ...
}
你还可以用同样的语法声明泛型的扩展属性。
val List.penultimate: T // 这个泛型扩展属性能在任何种类元素的列表上调用
get() = this[size - 2]
>>> println(listOf(1, 2, 3, 4).penultimate) // 在这次调用中,类型参数 T 被推导成 Int
3
可以给类或接口的方法、顶层函数,以及扩展函数声明类型参数。
范型约束
上界约束
fun oneHalf(value: T): Double { // 指定 Number 为类型形参的上界
return value.toDouble() / 2.0 // 调用 Number 类中的方法
}
制定多个上界(使用 where
)
fun ensureTrailingPeriod(seq: T) where T : CharSequence, T : Appendable { // 类型参数约束的列表
if (!seq.endsWith('.')) { // 调用为 CharSequence 接口定义的扩展函数
seq.append('.') // 调用 Appendable 接口的方法
}
}
事实上,没有指定上界的类型形参将会使用Any?
这个默认的上界。
class Processor {
fun process(value: T) {
value?.hashCode() // “value”是可空的,所以要用安全调用
}
}
非空上界
class Processor { // 指定非“空”上界
fun process(value: T) {
value.hashCode() // 类型 T 的值现在是非“空”的
}
}
类型擦除和实化类型参数
概念:
和 Java 一样,Kotlin 的泛型在运行时也被擦除了。这意味着泛型类实例不会携带用于创建它的类型实参的信息。例如,如果你创建了一个List
并将一堆字符串放到里面,在运行时你只能看到它是一个List
。不能识别出列表本打算包含的是哪种类型的元素。
在调用泛型函数的时候,在函数体中你不能决定调用它用的类型实参:
>>> fun isA(value: Any) = value is T
Error: Cannot check for instance of erased type: T
通常情况下都是这样,只有一种例外可以避免这种限制:内联函数。
如果你把前面例子中的isA
函数声明成inline
并且用reified
标记类型参数,你就能够用该函数检查value
是不是T
的实例。
inline fun isA(value: Any) = value is T // 现在代码可以编译了
>>> println(isA("abc"))
true
>>> println(isA(123))
false
另一个例子
val serviceImpl = ServiceLoader.load(Service::class.java)
现在让我们用带实化类型参数的函数重写这个例子:
val serviceImpl = loadService()
我们看看这个loadService
函数是如何定义的:
inline fun loadService() { // 类型参数标记成了“reified”
return ServiceLoader.load(T::class.java) // 把 T::class 当成类型形参的类访问
}
Android 中的使用
inline fun
Context.startActivity() { // 类型参数标记成了“reified” val intent = Intent(this, T::class.java) // 把 T::class 当成类型参数的类访问 startActivity(intent) } startActivity () // 调用方法显示 Activity
变型:协变与逆变
Java 中要声明一个协变的泛型类型,你只需要:
`List dogs = new ArrayList<>();List extends Animal> animals = dogs;`
extends
关键字将动物列表与狗狗列表之间的关系声明为了协变。
需要逆变的时候,只需:
`Compare animalCompare = (first, second) -> first.getSize() - second.getSize();Compare super Spider> spiderCompare = animalCompare;`
super
关键字将动物比较器与蜘蛛比较器之间的关系声明为了逆变。
Kotlin 中,要声明类在某个类型参数上是可以协变的,在该类型参数的名称前加上out
关键字就可以:
interface Producer { // 类被声明成在 T 上协变。
fun produce(): T
}
而逆变则是通过 in
关键字:
interface Comparator {
fun compare(e1: T, e2: T): Int { ... } // 在“in”位置使用 T。
}
类的类型参数前的out
关键字要求所有使用T
的方法只能把T
放在out
位置而不能放在in
位置。
一个类可以在一个类型参数上协变同时在另外一个类型参数上逆变。
interface Function1 {
operator fun invoke(p: P): R
}
型变是比较重要但不是像 1 + 1 = 2 这种概念这么容易理解,可以在具体使用过程中细细体会。
星号投影
- kt 中 <*> 等价于 java 中的 >
- 星号投影不能写入,只能读取
- 与 Any? 是不同的: List<*> 中的元素只能是一种数据类型,但 List
的元素可以是任意类型。