Kotlin第七講--泛型在Java和Kotlin上的差異

閱讀本文前,如果對Java泛型不夠了解,不妨先閱讀我之前寫的兩篇說Java泛型的文章

重拾Java泛型 上篇

重拾Java泛型 下篇

語法比較

泛型方法

Java

public static<T extends Comparable<? super T>> T max(List<? extends T> list){
        Iterator<? extends T> iterator = list.iterator();
        T result = iterator.next();
        while (iterator.hasNext()){
            T t = iterator.next();
            if(t.compareTo(result) > 0){
                result = t;
            }
        }
        return result;
    }

Kotlin

fun <T : Comparable<in T>> max(list: List<out T>): T {
    val iterator = list.iterator()
    var result: T = iterator.next()
    while (iterator.hasNext()) {
        val t = iterator.next()
        if (t.compareTo(result) > 0) {
            result = t
        }
    }
    return result
}

查看Kotlin裏的Comparable類和List類源碼,Comparable類定義,List類定義,所以可以不必重複表明Comparable的逆變能力和List的協變能力。

fun <T : Comparable<T>> max(list: List<T>): T {
    val iterator = list.iterator()
    var result: T = iterator.next()
    while (iterator.hasNext()) {
        val t = iterator.next()
        if (t.compareTo(result) > 0) {
            result = t
        }
    }
    return result
}

不管是泛型類還是泛型方法,泛型的使用分爲兩個步驟

  1. 第一步,聲明泛型參數
  2. 第二步,纔是使用泛型參數

上面的泛型方法,以Java泛型方法爲例,泛型聲明部分是:

<T extends Comparable<? super T>>

引入泛型類型名稱爲T,這個數據類型的關係是實現Comparable的類。

使用泛型的部分如下:

List<? extends T>

入參的集合具有對類型T的協變能力。

所以,有些書或者網上資料,會出現兩個概念:聲明點變型,使用點變型。這個概念說的就是這件事

再來看下in位置,和out位置的概念

in_out位置圖.png

所以,類型參數T上的關鍵字out有兩方面含義:

  1. 具有協變的能力
  2. T只能用在out位置

我們再看一個有趣的例子

我有一個方法,入參的一個集合,我希望入參集合元素是EditText的子類的集合,如何構建滿足這個關係的方法呢?至少有下面的兩種方式:

fun test2(list: MutableList<out EditText>){

}

fun <T: EditText>test3(list: MutableList<T>){

}

上面test2, test3方法都能實現預期的效果。不同的是,test2使用協變來實現,也意味着只能讀不能寫操作,test2不是泛型方法。test3方法通過在聲明表明泛型參數的類型關係來實現,可讀可寫,它是泛型方法。

對應的Java代碼如下

private void test2(List<? extends EditText> list){

}

private <T extends EditText> void test3(List<T> list){

}

實現泛型參數多約束

Java

private <T extends Serializable & CharSequence> void ensureTrailingPeriod(T seq){

}

kotlin

fun <T> ensureTrailingPeriod(seq: T) where T: Serializable, T: CharSequence{

}

這是Java語法上很大的不同,Kotlin要實現多約束,使用到where關鍵詞,這是全新的表示法。

實化類型參數

來看一段Java代碼:

private <T> void test(T t){
    if(t instanceof String){
        String s = (String)t;

    }else if(t instanceof Integer){
        Integer i = (Integer)t;
    }
}

入參是泛型的,通過instanceof來判斷泛型具體是哪個類型。再看一段代碼

private <T> void test2(Object o){
    //編譯不通過
    if(o instanceof T){

    }
}

泛型作爲參數類型時,編譯不通過。如果要完成上述的邏輯,要怎麼實現?可以通過class近似等效實現

class ClassJudge<T>{
    Class<T> kind;

    public ClassJudge(Class<T> kind) {
        this.kind = kind;
    }

    public boolean isInstance(Object o){
        return kind.isInstance(o);
    }
}

調用

    Integer a = 12;
    boolean instance = new ClassJudge<String>(String.class).isInstance(a);
    System.out.println("instance = " + instance);

Kotlin在語言層面作出了支持,對上述Java代碼test2方法,kotlin代碼如下:

inline fun<reified T> test2(any: Any){
    if(any is T){

    }
}

因此需要具備兩個條件:

  1. 是內聯函數 inline
  2. 關鍵詞 reified

集合協變

在Kotlin裏,非空類型是可空類型的子類型。

Java實現的協變

List<? extends Number> list = new ArrayList<Integer>();

Kotlin實現的協變

val list: MutableList<out Number> = arrayListOf<Int>()

集合逆變

Java實現的逆變

List<? super Integer> list = new ArrayList<Number>();

對應的Kotlin逆變的實現:

val list: MutableList<in Int> = arrayListOf<Number>()

變型(協變和逆變)涉及到集合元素,集合類。協變講的是兩個集合的元素是子類關係,這兩個集合也是子類關係,有了子類關係,就可以用多態表示。逆變的關係是反過來的,逆變說得是,兩個集合的元素是父類關係,這兩個集合卻能成爲子類關係。

由此可見,在泛型裏,extends 不全等於 :。extends代表子類型關係和協變,而Kotlin的 :只代表子類型關係。

<out T> = <? extends T>
<in T> = <? super T>

星投影和Java無限制通配符?差異

星投影如何使用

<*> = <out Any?>
<*> = <in nothing>

投影一詞,顧名思義,是對Any?的投影,獲得了Any?部分的能力—Any?協變的能力,即失去寫操作,只能讀操作。

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

場景二:泛型類存儲

所有不安全的邏輯都被隱藏在類的主體中,通過把這些邏輯局部化到一個分開的位置,預防了誤用,保證了它不會被錯誤地使用。

其他

非空性與泛型

Java和Kotlin默認,泛型參數都是可空的,以Kotlin爲例

class Processor<T> {
    fun process(value: T){
        value?.hashCode()
    }
}

泛型參數是可空類型,可以理解爲默認實現

class Processor2<T: Any> {
    fun process(value: T){
        value.hashCode()
    }
}

所以,泛型 和是有區別的,前者是可空類型,後者是非空類型,確切說,和

小結

Kotlin和Java的差異,體現在語法和功能兩個方面

語法上

  1. Kotlin使用:替代 extends;
  2. 用星投影 * 替代?
  3. 用out, in來體現協變和逆變;
  4. 泛型類型多約束條件,Kotlin使用where關鍵詞,而不用Java裏&表示

功能上:
1. 新增了實化參數類型

參考資料

仔細說說Java中的泛型

Java 泛型

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