Kotlin知識總結:泛型

基本使用

1.泛型接口

interface Generator<T>{
    fun next():T
}

 val gen = object :Generator<Int>{
        override fun next(): Int {
            return 1
        }
    }
    println("result: ${gen.next()} ")

這裏要用object關鍵字聲明Generator實現類
2.泛型類

class Container<K,V>(val key:K,val value:V){
    override fun toString(): String {
        return "Container ${key} ${value}"
    }
}

 val container = Container<Int,String>(1,"ABC")
   println(container)

多個泛型使用","隔開,因爲kotlin可以推斷類型,聲明類的時候,泛型也可以省略

val container = Container(1,"ABC")
    println(container)

3.泛型函數

fun <T> console(t:T){
    println(t)
}

< T>是類型形參聲明

類型擦除

JVM上的泛型一般是通過類型擦除實現的,就是說泛型類實例的類型實參在運行時是不保留的 。創建 一個 List<String>並將一堆字符串放到其中,在運行時只能看到它是一個 List。因爲類型實參沒有被存儲下來 ,你不能檢查它們 。 例如,你不能判斷一個列表是一個包含字符串的列表還是包含其他對象的列表。
在這裏插入圖片描述
類型擦除的好處是:應用程序使用的內存總量較小,因爲要保存在內存中的類型信息更少。
在Kotlin,函數聲明成 inline並且用 reified 標記類型參數,reified聲明瞭類型參數不會在運行時被擦除。
比如:

fun <T> printIfTypeMatch(item: Any) {
    if (item is T) { // IDE 會提示錯誤,Cannot check for instance of erased type: T
        println(item)
    }
}

加上reified

inline fun <reified T> printIfTypeMatch(item: Any) {
    if (item is T) { //  這裏不會提示錯誤
        println(item)
    }
}

其他情況:

☕️
TextView textView = new Button(context);
//  這是多態

List<Button> buttons = new ArrayList<Button>();
List<TextView> textViews = buttons;
//多態用在這裏會報錯 incompatible types: List<Button> cannot be converted to List<TextView>

這是因爲 Java 的泛型本身具有「不可變性 Invariance」,Java 裏面認爲 List 和 List 類型並不一致,也就是說,子類的泛型(List)不屬於泛型(List)的子類,可以用數組實現這樣的轉換,數組在編譯時是不會被擦除的

協變與逆變

一個泛型類一一例如 , MutableList一一如果對於任意兩種類型 A 和 B,MutableList<A>既不是 MutableList<B > 的子類型也不是它的超類型,它就被稱爲在該類型參數上是不變型的。如果 A 是 B 的子類型,那麼 List < A>就是 List<B>的子類型。這樣的類或者接口被稱爲協變的 。逆變就是,如果 B 是 A 的子類型,那麼 Consumer<A>就是 Consumer<B>的子類型。
在這裏插入圖片描述
在這裏插入圖片描述

out in

先看java的extends 和super,

List<Button> buttons = new ArrayList<Button>();
List<? extends TextView> textViews = buttons;

? extends 叫做「上界通配符」,可以使 Java 泛型具有「協變性 Covariance」,extends 限制了泛型類型的父類型,所以叫上界

List<? extends TextView> textViews = new ArrayList<Button>();
TextView textView = textViews.get(0); //  get 可以
textViews.add(textView);//add 會報錯,no suitable method found for add(TextView)

因爲有? extends TextView 的限制條件,所以 get 出來的對象,肯定是 TextView 的子類型,List<? extends TextView> 由於類型未知,它可能是 List< Button>,也可能是 List< TextView>,所以沒辦法確定類型,沒辦法add。只能夠向外提供數據被消費,「生產者 Producer」

List<? super Button> buttons = new ArrayList<TextView>();

? super 叫做「下界通配符」,可以使 Java 泛型具有「逆變性 Contravariance],限制了通配符 ? 的子類型,所以稱之爲下界

List<? super Button> buttons = new ArrayList<TextView>();
Object object = buttons.get(0); //  get 出來的是 Object 類型
Button button = ...
buttons.add(button); //  add 操作是可以的

Button 對象一定是這個未知類型的子類型,根據多態的特性,這裏通過 add 添加 Button 對象是合法的,使用下界通配符 ? super 的泛型 List,只能讀取到 Object 對象,通常也只拿它來添加數據,這種泛型類型聲明稱之爲「消費者 Consumer」

小結:
Java 的泛型本身是不支持協變和逆變的。
可以使用泛型通配符 ? extends 來使泛型支持協變,但是「只能讀取不能修改」,這裏的修改僅指對泛型集合添加元素,如果是 remove(int index) 以及 clear 當然是可以的。
可以使用泛型通配符 ? super 來使泛型支持逆變,但是「只能修改不能讀取」,這裏說的不能讀取是指不能按照泛型類型讀取,你如果按照 Object 讀出來再強轉當然也是可以的。
根據前面的說法,這被稱爲 PECS 法則:「Producer-Extends, Consumer-Super」
參考碼上開學Kotlin 的泛型
在JDK 1.7裏的java.util.Collection ,copy方法,可以說明out 和in 使用的情況
在這裏插入圖片描述
在這裏插入圖片描述
Kotlin:
使用關鍵字 out 來支持協變,等同於 Java 中的上界通配符 ? extends。
使用關鍵字 in 來支持逆變,等同於 Java 中的下界通配符 ? super。

符號

java中,單個"?“相當於? extends Object。
Kotlin中等效寫法,”*",相當於out Any。當類型實參的信息並不重要的時候,可以使用星號投影的語法:不需要使用任何在簽名中引用類型參數的方法,或者只是讀取數據而不關心它的具體類型。

fun printFirst(list: List<*>) {
    if (list.isNotEmpty()) {
            println(list.first())
        }
}

where

在java中,如果extends多個邊界,需要&符號連接

class Monster<T extends Animal & Food>{ 
}

在Kotlin中,可以使用where來連接

class Monster<T> where T : Animal, T : Food

java和kotlin泛型的區別:
Java 裏的數組是支持協變的,而 Kotlin 中的數組 Array 不支持協變
Java 中的 List 接口不支持協變,而 Kotlin 中的 List 接口支持協變

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章