天天看點

Java範型:通配符中super、extends的差別一、概要二、建構類樹三、代碼解讀

一、概要

java範圍有三種形式:

  1.  <? extends T>: 上界通配符, ?表示繼承自T的類(沿着類圖,上邊界是T)。頻繁往外讀取内容,适合采用上界通配符。
  2.  <? super T>:下界通配符,?表示T及其父類(沿着類圖,下邊界是T)。頻繁插入内容,适合采用下界通配符。
  3. <?>:某個類型。單純表示引用某一類型,不進行插入或讀取
  4. 如果頻繁讀取或插入,盡量避免使用通配符,以免資料丢失。

二、建構類樹

為了清晰表明概要中的意識,先建構類樹,用于概念細化、代碼解讀。此處解讀參考Think In Java書的案例。

class Fruit {
    public void getClassName() {System.out.println(this.getClass().getSimpleName());}
}
class Apple extends Fruit {}
class Jonathan extends Apple {}
class Maigold extends Apple {}
class Orange extends Fruit { }
class Mandarin extends Orange {}
class Tangerine extends Orange {}
           
Java範型:通配符中super、extends的差別一、概要二、建構類樹三、代碼解讀

以上即為本次案例所使用的類圖及繼承關系

Java範型:通配符中super、extends的差別一、概要二、建構類樹三、代碼解讀

如上圖,

  • <? extends Apple>為上界通配符,即上界是Apple,?表示繼承自Apple的類,作用範圍是紅色三角内部的類
  • <? super Apple>為下界通配符,即下界是Apple,?表示Apple及其父類,作用範圍是藍色三角内部的類。

三、代碼解讀

3.1 泛型容器無法進行向上轉型

import java.util.ArrayList;
import java.util.List;

public class NonCovariantGenerics {
    // 編譯錯誤,無法進行向上轉型
    List<Fruit> flist = new ArrayList<Apple>();
}
           

上述代碼會引發編譯錯誤,無法将List<Apple>向上轉型為List<Fruit>,即無法将“涉及Apple的範型賦給涉及Fruit的範型”。當然這違背生活常識,Apple竟然無法放到Fruit裡。但從代碼角度思考,Apple的List不是Fruit的List,因為List<Apple>将持有Apple及其子類,而List<Fruit>能夠持有Fruit子類,範圍遠大于List<Apple>。

如果想在泛型容器間建立轉型關系,使代碼描述更加人性化,即面向對象,則需要采取通配符。

3.2 上界通配符 <? extends T>

import java.util.ArrayList;
import java.util.List;

public class GenericsAndCovariance {
    public static void main(String[] args) {
        // 上界通配符進行容器的向上轉型
        List<? extends Fruit> flist = new ArrayList<Apple>();
        // 以下均會引起編譯錯誤
        // flist.add(new Apple());
        // flist.add(new Fruit());
        // flist.add(new Object());
        flist.add(null); // Legal but uninteresting
        // We know that it returns at least Fruit:
        Fruit f = flist.get(0);
        f.getClassName();   //List裡隻有空指針,無法調用該函數
    }
} 
           

通過上述代碼例子得知,List<? extends Fruit>并不是說該List可以持有任何類型的Fruit,通配符引用的是明确的類型。是以,該處可以這樣解譯“List<? extends Fruit> 持有某個具體類型,該類型具體是什麼我不清楚,但我可以具定它是Fruit的子類型”。

可見,因為無法确定List<? extends Fruit>具體持有什麼類型,故向内部填加對象比較危險,故調用add(Object object)會引發失入。但是可以使用get(int index)方法。

可見,上界通配符主要支援讀取操作,應盡量避免寫入操作,以免引發異常。

3.3 繞過編譯器

import java.util.*;

public class CompilerIntelligence {
    public static void main(String[] args) {
        List<? extends Fruit> flist =
                Arrays.asList(new Apple(), new Fruit(), new Orange(), new Jonathan());

        Apple apple = (Apple)flist.get(0); // 利用get擷取類
        apple.getClassName();

        for (int i = 1; i < flist.size(); i++) {
            Fruit temp = flist.get(i); //利用動态編譯,指向不同的類
            temp.getClassName();
        }

        flist.contains(new Apple()); // 調用boolean contains(Object o);
        flist.indexOf(new Apple()); // 調用int indexOf(Object o);
        //flist.add(new Apple()); //調用boolean add(E e);失敗
    }
}
/**
 * OUTPUT
 * Class name is : Apple
 * Class name is : Fruit
 * Class name is : Orange
 * Class name is : Jonathan
 */
           

上述代碼可知:

  • 借助Arrays.asList将Fruit及其子類,以List的形式存儲到List<? extends Fruit>,變相實作了add,隻是有些機械化。
  • 利用強制轉型,擷取對應的Apple類型
  • 利用動态編譯,動态引用Orange, Jonathan類型
  • contains 和indexOf方法的參數是Object,不涉及通配符,是以可以成功執行
  • add的方法參數是泛型,在本案例中即是<? extends Fruit>,是通配符,無法明确具體類型,是以編譯失敗

3.4 下界通配符<? super T>

下界通配符,直白些,可以了解為支援向下轉型,其作用域可以參見本篇第二章圖的藍色區域

import java.util.*;

public class SuperTypeWildcards {
    public static void main(String[] args ) {
        //支援向下轉型,不支援向上轉型
        List<? super Apple> appleList = new ArrayList<Fruit>();
        //List<? super Apple> appleListTwo = new ArrayList<Jonathan>();轉型失敗
        //appleList = new ArrayList<Jonathan>();轉型失敗,無法進行
        appleList.add(new Apple());
        //appleList.add(new Fruit());編譯失敗,List<Fruit>可以向下轉型為List<? super Apple>,
        // 但是轉型後,就隻能放Apple相關類了
        appleList.add(new Jonathan());
        appleList.add(new Maigold());
        //appleList.add(new Orange());編譯失敗,Orange不是Apple
        for (int i = 0; i < appleList.size(); i++) {
            Apple temp = (Apple) appleList.get(i);
            temp.getClassName();
        }
    }
}
/**OUTPUT
 * Class name is : Apple
 * Class name is : Jonathan
 * Class name is : Maigold
 */
           

上述代碼得知

  • <? super T>支援向下轉型,不支援向上轉型,作用範圍參見本篇圖2。我把這了解為下界通配符的通俗含義
  • <? super T>支援插入資料類,插入的資料必須是Apple及其子類
  • <? super T>通過強制類型轉換,也可以取資料,可能會存在資料丢失情況

3.5 無界通配符 <?>

用一句歌詞來表達無界通配符,“我不知道你是誰,但我知道你為了誰”。無界通配符作用等價于使用原生類型,其表明使用泛型進行類型引用,在不清楚或者無需清楚具體類型的前提下。