16.Kotlin星投影與泛型約束詳解

weixin_34185364發表於2018-02-26

星投影

star projection(星投影)

Star<out T>:如果T的上界是TUpper,那麼Star<*>就相當於Star<out T>,這表示當T的型別未知時,你可以Star<*>當中安全地讀取TUpper型別的值。

Star<in T>:Star<*>就相當於Star<in Nothing>,這表示你無法向其中寫入任何值。

Star<T>,如果T的上界為TUpper,那麼Star<*>就相當於讀取時的Star<out TUpper>以及寫入時的Star<in Nothing>

如果泛型型別具有多個型別引數,則每個型別引數都可以單獨投影。 例如,如果型別被宣告為interface Function <in T, out U>,我們可以想象以下星投影:

Function<*, String>表示 Function<in Nothing, String>
Function<Int, *> 表示Function<Int, out Any?>
Function<*, *> 表示 Function<in Nothing, out Any?>

注意:星投影非常像 Java 的原始型別,但是安全。

示例程式碼

class Start<out T> {

}

class Star2<in T> {
    fun setValue(t: T) {

    }
}

class Star3<T>(private var t: T) {
    fun setValue(t: T) {

    }

    fun getValue(): T {
        return this.t
    }
}

fun main(args: Array<String>) {
    val star: Start<Number> = Start<Int>()
    val star2: Start<*> = star

    val star3: Star2<Int> = Star2<Number>()
    val star4: Star2<*> = star3

    //star4.setValue(3) //compile error

    val star5: Star3<String> = Star3<String>("hello")
    val star6: Star3<*> = star5

    star6.getValue()
    //star6.setValue("") //compile error

    val list: MutableList<*> = mutableListOf("hello", "world", "hello world")
    println(list[0])

    //list[0] = "test" //compile error
}

泛型擦除

Kotlin 為泛型宣告用法執行的型別安全檢測僅在編譯期進行。 執行時泛型型別的例項不保留關於其型別實參的任何資訊。 其型別資訊稱為被擦除。例如,Foo<Bar> 與 Foo<Baz?> 的例項都會被擦除為 Foo<*>。

示例程式碼

package com.leofight.kotlin2

class MyStorage<out T>(private var t: T) {

    fun getValue(): T {
        return this.t
    }

    fun setValue(t: @UnsafeVariance T) {
        this.t = t
    }
}

fun main(args: Array<String>) {
    var myStorage1: MyStorage<Int> = MyStorage(5)
    val myStorage2: MyStorage<Any> = myStorage1

    println(myStorage2.getValue())

    myStorage2.setValue("hello")//泛型擦除

    println(myStorage2.getValue())
}

泛型函式

不僅類可以有型別引數。函式也可以有。型別引數要放在函式名稱之前:

fun <T> getValue(item: T): T {
    return item
}

要呼叫泛型函式,在呼叫處函式名之後指定型別引數即可:

fun main(args: Array<String>) {
    val item = getValue<Int>(3)
    println(item)

    val item2 = getValue("hello")
    println(item2)
}

泛型約束

能夠替換給定型別引數的所有可能型別的集合可以由泛型約束限制。

上界

最常見的約束型別是與 Java 的 extends 關鍵字對應的 上界:

fun <T : Comparable<T>> sort(list: List<T>) {
    // ……
}

冒號之後指定的型別是上界:只有 Comparable<T> 的子型別可以替代 T。 例如:

sort(listOf(1, 2, 3)) // OK。Int 是 Comparable<Int> 的子型別
sort(listOf(HashMap<Int, String>())) // 錯誤:HashMap<Int, String> 不是 Comparable<HashMap<Int, String>> 的子型別

預設的上界(如果沒有宣告)是 Any?。在尖括號中只能指定一個上界。 如果同一型別引數需要多個上界,我們需要一個單獨的 where-子句:

fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String>
    where T : CharSequence,
          T : Comparable<T> {
    return list.filter { it > threshold }.map { it.toString() }
}

相關文章