Kotlin基础知识九: Generics

2020-05-13  本文已影响0人  北雁南飞_8854

一、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 具体化的类型参数
  1. inline函数:
    Inlining works best for functions with parameters of functional types.
上一篇下一篇

猜你喜欢

热点阅读