對java以及Kotlin中泛型型變的相關認識!

   ●泛型是一個很令人費解的概念,但卻是理解類型系統的重要基石!

大部分內容摘自《kotlin極簡教程》,加上了自己的一些理解,寫的不好,請多指教!

型變有三種基本的形態:協變(covariant), 逆變(cotravariant),不變(invariant)

我們先從java瞭解的類型通配符介紹這三個概念:java中通配符有一下兩種形式:

        ▲? extends T:子類型上界限定符,指定類型的上界(只能是T類型或者T的子類)

        ▲? super T :父類的下界限定符,指定類型的下界(只能是T類型或者是T類型的父類)

interface Animals{
    void eat();
    void run();
}
class Cat implements Animals{
    @Override
    public void eat() {}
    @Override
    public void run() { }
}
class Dog implements Animals{
    @Override
    public void eat() {}
    @Override
    public void run() {}
}

在這裏我先用java寫幾個類,在java中我們知道:Animal是Cat和Dog的父類,可以直接使用Cat的子類和Dog的

子類給Animals的一個對象賦值,那麼ArrayList<Animals> animals = new ArrayList<Cat>()或者

ArrayList<Animals> animals = new ArrayList<Dog>()還可以嗎?或者說ArrayList<Animals>還是

ArrayList<Cat>()或者ArrayList<Dog>的父類嗎?

我們試試看:


可以看到編譯報錯,是不可能這樣賦值的!

那麼泛型通配符就派上用處了。將ArrayList<Animal> animals改成 ArrayList<? extends Animal> :


也就是說ArrayList<Animals>並不是ArrayList<Cat>()和ArrayList<Dog>的父類,而ArrayList<? extends Animals>纔是!

但是這裏的ArrayList<? extends Animals>並不能添加Animal及其子類對象的!

因爲如果能的話就既能添加Cat對象又能添加Dog對象,java是不允許的!但是能add(null)

上述這種情況就叫做協變(即F是C的父類,f(F)是f(C)的父類)所以<? extends T>實現了泛型的協變!(f(F)類似於List<F>的形式)

●<? super T>實現了泛型的逆變!

逆變即(即F是C的父類,f(C)f(F)的父類)

如果既不是逆變又不是協變,就是不變。

List<? super Number>,這裏限制了List的內容只能是Number和Number父類。例如List<Object> 就是他的子類,而Object是所有類的父類,這就是逆變!看代碼:

ArrayList<Number> numbers = new ArrayList<Object>()報錯!

ArrayList<? super Number> numbers = new ArrayList<Object>()編譯通過!

在這個list中我們不能添加Number的任一父類對象,但能添加Number和其所有子類的對象!


●PECS(Producer-Extends, Consumer-Super)

那麼我們什麼時候使用extends,什麼時候使用super呢!

《kotlin極簡教程》書上用了一個stack API來舉例說明:

public class Stack<E>{
    public Stack(){}
    public void push(E v){}
    public E pop(){}
    public boolean isEmpty(){}
}

實現pushAll方法

    public void pushAll(Iterable<E> src){
        for(E e : src)
            push(e)
    }

假設有一個實例化的stack<Number>,要將stack<Integer>和stack<Float>裏的所有值添加進去,調用上面的方法會出現type mismatch錯誤應該寫成下面這種形式。

    public void pushAll(Iterable<?extends E> src){
        for(E e : src)
            push(e)
    }

而要實現popAll方法,將一個stack中的元素依次取出add至dst中,不用通配符時:

    public void popAll(Stack<E> dst) {
        while(!isEmpty())
            dst.push(pop())
    }

如果要將一個stack<Number>棧全部輸出至一個stack<Object>中, 即dst是stack<Object>類,則一樣會出現type mismatch錯誤,應將方法改成:

    public void popAll(Stack<? super E> dst) {
        while(!isEmpty())
            dst.push(pop())
    }

將其PECS稱爲Get and Put principle

在java.util.Collection的copy方法中完美地詮釋了PECS:

    public static <E> void copy(List<? super E> dest, List<? extends E> src) {
        int srcSize = src.size();
        if(srcSize > dest.size())
            throw new IndexOutOfBoundsException("Source does not fit in dest");
        ...略
    }

大致可以這麼解釋,在這個方法中,我們從src中取數據,即src是producer,然後將數據添加至dest,所以dest是consumer所以,PECS,前者就是super,後者就是extends。

●kotlin:

     java中有通配符,kotlin中則拋棄了這些,引用了生產者消費者概念,從上面的copy方法中也可以看出端倪。

kotlin把只負責安全讀取數據的對象稱爲生產者,只負責安全寫入數據的對象爲消費者。kotlin中,生產者用

<out T>(等價於<? extends T>),消費者用<in T>(等價於<> super T>)。


寫不下去了。。。

    












發佈了41 篇原創文章 · 獲贊 22 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章