28.Android架構-泛型通配符

我們有一下幾個類,一個Fruit,一個Apple,Apple 繼承 Fruit,是Fruit的子類。還有一個Plate(盤子)接口,用來裝水果,ApplePlate類,專門裝蘋果


interface Plate<R>{
    void set(R r);
    R get();
}

class ApplePlate implements Plate<Apple> {
    @Override
    public void set(Apple apple) {

    }

    @Override
    public Apple get() {
        return null;
    }
}


class Fruit{

}

class Apple extends Fruit{
    @Override
    public String toString() {
        return super.toString();
    }
}


我通過泛型指定了這個盤子是用來裝蘋果的

    public static void main(String[] args) {
        Plate<Apple> applePlate = new ApplePlate<>();
    }

假如這個時候,我蘋果喫完了,我就想用這個AppPlate去裝其他水果,可不可以呢?因爲所有的水果都繼承自Fruit,所以我要把這個蘋果AppPlate強轉成裝水果的Plate,但是直接在編譯期就報錯了,無法進行強轉這是爲什麼呢?我們知道可以強轉的兩個類應該符合A is a B的關係,A才能強轉成B,那麼Apple繼承自Fruit,Apple is a Fruit是符合的,但是Plate<Apple>滿足is a Plate<Fruit> 的關係嗎,答案是否定的。給定兩種具體的類型A和B(例如Fruit和Apple), 無論A和B是否相關, MyClass<A>與MyClass<B>都沒半毛錢關係, 它們的公共父對象是Object

public static Plate<Fruit> getFruitPlate(Plate<Apple> plate){
        Plate<Fruit> fruitPlate = plate;  //錯誤
        return fruitPlate;
    }

只有滿足下邊這種關係,才能說他們有is a 的關係


上界通配符<? extends R>:能取不能存

那麼如何讓水果盤子和蘋果盤子直接發生關係呢,答案就是通配符<?>

我們把getFruitPlate方法改造一下

public static Plate<? extends Fruit> getFruitPlate(Plate<Apple> plate){
        Plate<? extends Fruit> fruitPlate = plate;
        return fruitPlate;
    }

Plate<? extends Fruit>叫上限通配符,他是Plate<Fruit> 和Plate<Apple>的基類,所以用子類給基類賦值是行得通的。他限定的是上邊界Fruit,只有Fruit及其子類可以作爲<? extends Fruit>的子類,從而實現賦值


那麼轉成Plate<? extends Fruit>之後,我是不是可以往他裏邊放水果了,比如apple或者其他fruit類型,然後並非這樣。如何解釋呢,其實可以這樣理解,既然Plate<? extends Fruit>是Plate<Fruit> 和Plate<Apple>的基類,那麼其實他的真正類型只有在運行時才能確定,那麼所以他的類型可能是Apple也可能是Orange等等Fruit的其他子類,所以這個情況下,他的真正類型是不確定的,所以無法往裏邊set,但是有一點,無論他的真正類型是什麼,他都是一種Fruit,所以通過get方法取是可以的

public static Plate<? extends Fruit> getFruitPlate(Plate<Apple> plate){
        Plate<? extends Fruit> fruitPlate = plate;
        fruitPlate.set(new Apple());  //報錯
        Fruit fruit = fruitPlate.get();  //正確
        return fruitPlate;
    }

那麼有沒有辦法破解這個問題呢,有的,反射。但是比較危險,見下圖


下界通配符<? super R>:能存不能取

下界通配符限定的是下邊界,如下圖,Plate<? super Fruit>是Plate<Fruit>的基類


我們再增加一個類型Food,水果Fruit也屬於Food的一種,所以Fruit繼承自Food

class Food{
    
}

class Fruit extends Food{

}

class Apple extends Fruit{
    @Override
    public String toString() {
        return super.toString();
    }
}

增加一個放Food的盤子FoodPlate

class FoodPlate implements Plate<Food> {

    @Override
    public void set(Food food) {
        
    }

    @Override
    public Food get() {
        return null;
    }

}

可以看到,可以往裏邊放Apple Banana等水果,因爲無論是哪種水果,都是屬於Fruit類型,但是往外取的時候,泛型信息丟失了,只能通過Object接收了

<?> :不能存也不能取

Plate<?>其實就是Plate<? extends Object>

總結:Java泛型PECS原則

如果你只需要從集合中獲得類型T , 使用<? extends T>通配符
如果你只需要將類型T放到集合中, 使用<? super T>通配符
如果你既要獲取又要放置元素,則不使用任何通配符。例如List<Apple>
PECS即 Producer extends Consumer super, 爲了便於記憶。

爲何要PECS原則?提升了API的靈活性

應用java.util.Collections類中的copy方法

爲什麼dest要使用<? super T> 而src要使用<? extends T>?就是因爲要從src裏取出,存在dest裏

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

        if (srcSize < COPY_THRESHOLD ||
            (src instanceof RandomAccess && dest instanceof RandomAccess)) {
            for (int i=0; i<srcSize; i++)
                dest.set(i, src.get(i));
        } else {
            ListIterator<? super T> di=dest.listIterator();
            ListIterator<? extends T> si=src.listIterator();
            for (int i=0; i<srcSize; i++) {
                di.next();
                di.set(si.next());
            }
        }
    }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章