一、Generic Types
声明一个或多个形参(type parameters),例如class Map拥有K和V两个类型参数:class Map<K, V>。当一个实例被创建时,会使用特定的实参类型(type arguments)替换形参(type parameters)。
一般来说,实参类型可以由kotlin编译器推断而出,例如:
val authors: List<String> = listOf("Dmitry", "Svetlana")
可以省去显式的类型声明(explicit type specification)。如果创建的是一个空的list,就无法推断出实参类型,因此需要显式的指定它:
val readers: MutableList<String> = mutableListOf()
val readers = mutableListOf<String>()
与Java不同的是,Kotlin不支持原生类型(raw types),一个原生类型就是一个没有任何类型参数的泛型类,因此定义一个generic type时必须要指定形参类型(type arguments)。
二、Generic Functions
fun关键字后面紧跟<T>表示声明类型为T的形参(Type parameter)。例如:
fun <T> List<T>.slice(indices: IntRange): List<T>
fun <T> List<T>.filter(predicate: (T) -> Boolean): List<T> {/* ... */ }
fun main() {
val authors: List<String> = listOf("Dmitry", "Svetlana")
val readers = mutableListOf<String>(/* ... */)
readers.filter { it !in authors }
}
三、Generic Classes
定义一个Kotlin的泛型类List<T>如下:
interface List<T> {
operator fun get(index: Int): T
// ...
}
其中尖括号括起来的T表示定义了一个形参类型(type parameter)T,然后T可以在class或interface中当成一个常规类型使用。
3.1 Generic class的继承
class StringList : List<String> {
override fun get(index: Int): String {
TODO("Not yet implemented")
}
}
class ArrayList:List<T> {
override fun get(index: Int): T {
TODO("Not yet implemented")
}
}
一个class可以把自身当做实参(type argument)。
例如:
interface Comparable<T> {
fun compareTo(other: T): Int
}
class String : Comparable<String> {
override fun compareTo(other: String): Int = /* ... */
}
String类实现了泛型的Comparable接口,并为形参类型T提供了String类型。
四、类型参数约束(Type parameter constraints)
4.1 上限约束(upper bound constraint)
fun <T : Number> List<T>.sum(): T
其中,T为Type parameter,T后面紧跟的分号加上Number表示upper bound。
对应的Java语法为:
<T extends Number> T sum(List<T> list);
看另外一个例子:
fun <T: Comparable<T>> max(first: T, second: T): T {
return if (first > second) first else second
}
形参T的upper bound是一个泛型类型Comparable<T>。
4.2 为形参定义多个约束
fun <T> ensureTrailingPeriod(seq: T)
where T : CharSequence, T : Appendable {
if (!seq.endsWith('.')) {
seq.append('.')
}
}
fun main() {
val helloWorld = StringBuilder("Hello World")
ensureTrailingPeriod(helloWorld)
println(helloWorld)
}
4.3 使形参非空
当定义一个泛型类或函数时,实参类型可以是任何类型(包括Nullable)。没有指定约束的形参类型默认上界是Any?。
class Processor<T> {
fun process(value: T) {
println(value?.hashCode())
}
}
fun main() {
val nullableStringProcessor = Processor<String?>()
nullableStringProcessor.process(null)
}
如果想指定一个non-null的类型约束,可以使用Any作为上界。
class Processor<T : Any> {//指定non-null的upper bound,类型T必须得是一个non-nullable类型。
fun process(value: T) {
println(value.hashCode())
}
}
这时,Processor<String?>()会编译失败:
>>> val nullableStringProcessor = Processor<String?>()
Error: Type argument is not within its bounds: should be subtype of 'Any'
五、泛型的运行时:类型擦除(type erasure)和具体化类型参数(reified type parameters)
5.1 运行期的类型擦除(type erasure)
编译器在运行期(runtime)不会保留一个泛型类实例的实参类型(type arguments)。
5.2 类型检验(type check)
与Java相同,Kotlin的Generics在运行时的类型是会被擦除的。也即一个Generic类的实例在运行期不会携带创建该实例时的实参类型(type arguments)。因此,对泛型类型实例使用is检验会导致编译失败(编译器已经已知的类型除外)。举例:
fun main() {
val list1: List<String> = listOf("a", "b")
if(list1 is List<String>) { //Warn: Check for instance is always 'true'
println("List<String>")
}
if(list1 is List<Int>) { //Compile error: Cannot check for instance of erased type: List<Int>
println("List<Int>")
}
}
对generic function也是同理,在function body中无法确定被调用的实参类型(type argument)。举例:
fun <T> isA(value:Any) = value is T //compile error: Cannot check for instance of erased type: T
如果把isA函数声明为inline、同时把形参声明为reified,那么这时候可以在运行期获悉被调用的是参类型。
inline fun <reified T> isA(value:Any) = value is T
fun main() {
println(isA<String>("abc"))
println(isA<String>(123))
}
另外一个例子是系统的filterIsInstance函数:
fun main() {
val items = listOf("one", 2, "three")
println(items.filterIsInstance<Int>()) //[2]
println(items.filterIsInstance<String>()) //[one, three]
}
filterIsInstance函数的简单实现如下:
inline fun <reified T>
Iterable<*>.filterIsInstance(): List<T> {
val destination = mutableListOf<T>()
for (element in this) {
if (element is T) {
destination.add(element)
}
}
return destination
}
其中,reified关键字声明了实参类型(type argument)在运行期不会被擦除。
5.3 类型转换(type check)
fun printSum(c: Collection<*>) {
val intList = c as? List<Int> //compile warn: Unchecked cast: Collection<*> to List<Int>
?: throw IllegalArgumentException("List is expected")
println(intList.sum())
}
fun main() {
printSum(listOf(1, 2, 3)) //6
printSum(setOf(1,2,3)) //run error: java.lang.IllegalArgumentException: List is expected
printSum(listOf("Java", "Kotlin")) //run error: java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Number
}
5.5 具体化的类型参数
- inline函数:
Inlining works best for functions with parameters of functional types.
网友评论