Kotlin 之泛型詳解

泛型約束

fun <T : Comparable<T>> maxOf(a: T, b: T): T {
    if (a > b) {
        return a
    }
    return b
}

fun <T> callMax(a: T, b: T) where T : Comparable<T>, T : () -> Unit {
    a()
    b()
}

泛型的型變

泛型型變分爲三種

  • 不變

    沒有繼承關係,傳什麼就是什麼,只有一個類型

  • 協變: out

    • 只讀不寫
    • 父類出現的地方可以用子類代替

    只讀不寫:

    val a: Array<out Number> = Array<Int>(2, { 3 })
    a[0]    //可讀
    a[0] = 2 //ERROR
    
    open class W<out K> {
    	//Error 不可寫
        fun add(k: K) {
    
        }
    	//可讀
        fun get(): K? {
            return null
        }
    }
    

    父類出現的地方,可以使用代替。類似<? extends T>

    fun main() {
    
        val b = Box<Number>()
        b.a(Array<Int>(2, { 5 }))//error
    
    }
    class Box<E> {
    
        fun a(value: Array<E>) {
    
        }
    }
    

    因爲 E 是 Number,所以傳入 Int 肯定是錯誤的。修改如下:

    fun a(value: Array<out E>) {}
    

    out 代表協變,表示繼承自 E 的都可以。這個時候傳入 Int 就沒問題了。因爲 Int 是繼承 Number 的。

    注意:

    class Box<E> {
    
        fun a(value: Array<out E>) {
            value[0] =1 //Error
        }
    }
    
    
  • 逆變:in

    • 只寫不讀。某些情況可以讀,但是隻能往外取放在 Object 中

    • 子類出現的地方,可以使用父類代替

    只寫不讀

    val a: Array<in Int> = Array<Number>(2, { 3 })
    val s = a[0]  // 不能確定返回值的類型,只能使用Any類型接收
    a[0] = 2 
    
    open class W<in K> {
    
        //可寫
      fun add(k: K) {
    
      }
    	//返回值 K 報錯,不可讀
        fun get(): K? {
            return null
      }
    }
    

    子類出現的地方,可以使用父類代替

    fun main() {
    
        val b = Box<Int>()
        b.b(Array<Number>(2, { 5 }))//error
    
    }
    
    class Box<E> {
        fun b(value: Array<E>) {
    
        }
    }
    

    E 是 Int,傳入的是 Number。所以報錯,修改如下

    fun b(value: Array<in E>) {}
    

    in 代表逆變,表示只要是 E 的父類都可以。所以傳入 Number 就沒問題了。Number 是 int 的父類

    注意:

     fun b(value: Array<in E>) {
            val s = value[0]    //不能確定返回值的類型,只能使用Any類型接收
    }
    

UnsafeVariance

違法形變約束

  • 即聲明爲協變的類出現逆變,或者相反
  • 聲明爲不變的類接收協變或者逆變類型的參數
class Dustbin<in T> {

    //報錯,mutableListOf 本身是協變,但是傳入的是逆變,所以報錯。
    val list = mutableListOf<T>()

    fun put(t: T) {
        list += t
    }
    
}

修改如下:

val list = mutableListOf<@kotlin.UnsafeVariance T>()//使用註解,忽略形變即可

星投影

  • ‘*’ 可以用在變量類型的聲明位置

  • ‘*’ 可以描述一個未知的類型

  • ‘*’ 所替換的類型在:

    • 協變點返回泛型參數上限類型
    • 逆變點接受泛型參數下限類型
  • 不能直接或間接使用在屬性或者函數上

  • 對於 Foo ,其中 T 是一個具有上界 TUpper 的協變類型參數,Foo <> 等價於 Foo 。 這意味着當 T 未知時,你可以安全地從 Foo <> 讀取 TUpper 的值。

  • 對於 Foo ,其中 T 是一個逆變類型參數,Foo <> 等價於 Foo 。 這意味着當 T 未知時,沒有什麼可以以安全的方式寫入 Foo 。

  • 對於 Foo ,其中 T 是一個具有上界 TUpper 的不型變類型參數,Foo<*> 對於讀取值時等價於 Foo 而對於寫值時等價於 Foo。

out:當接收可協變的泛型參數(out T )時, * 映射的類型爲 Any?

open class W<out K> {
	
    //Error
   /* fun add(k: K) {

    }*/

    fun get(): K? {
        return null
    }
}

fun main() {

    val w = W<Number>()
    val w1: W<*> = W<Number>()
    val s = w1.get() //返回類型爲 Any?


}

下限:當接收可逆變泛型參數(in T) 時,* 映射類型爲 Nothing。

注意:因爲下限無法定義,所以所有的下限類型都爲 Nothing

fun main() {

 
    val w1: W<*> = W<Number>()
    w1.add("Nothing")//報錯,只支持 Nothing

}

open class W<in K> {

    fun add(k: K) {

    }
}

泛型實現原理

​ 僞泛型:如果使用了泛型,則會在編譯時擦除類型,運行時無實際類型生成。這點和 Java一樣

​ 真泛型:C#

泛型類型無法當做真實類型

fun <T> genericMethod(t: T) {
    val t = T() //Error
        val ts = Array<T>(3){ TODO() } //Error ,Array 在編譯的時候類型時確定的,不會擦除
    val cls = T::class.java //Error 無法獲取 class
    val list = ArrayList<T>()   //可以
}

內聯特化

​ 如果一個函數是內聯函數,那麼他會在調用處執行。一旦在調用的地方執行內聯函數體,我們就可以知道他的類型時確定的了。

​ 聲明如下:

inline fun <reified T> genericMethod(t: T) {
    val t = T() //Error
    val ts = Array<T>(3){ TODO() } 
    val cls = T::class.java 
    val list = ArrayList<T>()
}

例子:

fun <T> fromJson(json: String, classOft: Class<T>): T {

}

fun <T> test(json: String) {
    fromJson(json, T::class.java)//Error
}

在調用 fromJson 的時候,無法傳入 T 的class。因爲 T 在編譯的時候會被擦除。

這個時候就可以曲線救國:使用 內聯特化:

inline fun <reified T> test(json: String) {
    fromJson(json, T::class.java)//Success
}

這個函數中的代碼會被內聯到調用處,所以泛型的內心自然也就曉得了。

  • 泛型原理
    • 泛型實現
      • 僞類型擦除:在編譯時會擦除爲上限類型
      • 真:類型生成
    • 內聯特化
      • 擴充僞泛型的限制

案例:

​ 一般情況下,在繼承關係中,子類可以調用父類的方法,但是父類無法調用子類方法。但是通過接口和泛型可以實現調用子類方法:

typealias OnConfirm = () -> Unit
typealias OnCancel = () -> Unit

private val EmptyFunction = {}

open class Notification(
    val title: String,
    val content: String
)


class ConfirmNotification(
    title: String,
    content: String,
    val onConfirm: OnConfirm,
    val conCancel: OnCancel
) : Notification(title, content)


/**
 * 實現 SelfType 接口。泛型是 NotificationBuild 的子類
 * 注意 SelfType 有一個屬性 self。是泛型的類型
 * 所以 可以通過返回的 self 調用子類的方法。
 */
open class NotificationBuild<Self : NotificationBuild<Self>> : SelfType<Self> {
    protected var title = ""
    protected var content = ""

    fun title(title: String): Self {
        this.title = title
        return self
    }

    fun content(content: String): Self {
        this.content = content
        return self
    }

    open fun build() = Notification(title, content)

}

//將子類類型作爲泛型傳給父類
class ConfirmNotificationBuilder : NotificationBuild<ConfirmNotificationBuilder>() {
    protected var onConfirm: OnConfirm = EmptyFunction
    protected var onCancel: OnCancel = EmptyFunction

    fun onConfirm(onConfirm: OnConfirm): ConfirmNotificationBuilder {
        this.onConfirm = onConfirm
        return this
    }

    fun onCancel(onCancel: OnCancel): ConfirmNotificationBuilder {
        this.onCancel = onCancel
        return this
    }

    override fun build(): ConfirmNotification {
        return ConfirmNotification(title, content, onConfirm, onConfirm)
    }
}

interface SelfType<Self> {
    //將 this 強轉爲 泛型的類型
    val self: Self
        get() = this as Self
}


fun main() {
    //創建 ConfirmNotificationBuilder  對象時,就將當前類作爲泛型傳給父類了
    ConfirmNotificationBuilder()
        .title("標題") 
        .content("內容")
        .onCancel {
            println("OnCancel")
        }
        .onConfirm {
            println("OnConfirm")
        }
        .build()
        .onConfirm()
}
  • 泛型

    • 基本概念:1,泛型參數,2,函數,類添加泛型

    • 泛型約束:

      • 泛型上限:通過 where 語句添加多個上限
    • 泛型形變

      • 不變:指定的泛型類型
      • 協變:只讀不寫,意思是隻能讀取,不能寫入
      • 逆變:只寫不讀,某些情況可以讀,但是隻能是 Any 類型
    • 泛型形變點

      • 協變點:函數返回值類型爲泛型參數類型
      • 逆變點:函數參數爲泛型參數類型
    • UnsafeVariance

      • 型變點與泛型不一致是使用這個註解
      • UnsafeVariance
    • 星投影

      • 協變向上,獲取上限
      • 逆變向下,獲取下限,所以類型的下限爲 Nothing,不可被添加。
    • 泛型原理和內聯特化

      • 泛型的擦除,在編譯時,泛型的類型會被擦除。
      • 調用某一個泛型方法,無法直接使用泛型,這時就可以使用內聯特化。該方法會在調用處執行。泛型是明確的。
    • 模擬Self Type

      • 通過泛型參數拿到子類的類型,從而可以調用子類的方法
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章