美文网首页
Kotlin基础知识九: Generics

Kotlin基础知识九: Generics

作者: 北雁南飞_8854 | 来源:发表于2020-05-13 22:24 被阅读0次

    一、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.

    相关文章

      网友评论

          本文标题:Kotlin基础知识九: Generics

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