1. 程式人生 > >Android開發之深入理解泛型extends和super的區別

Android開發之深入理解泛型extends和super的區別

我想 lis dataset 文檔 cnblogs extend 擦除 選擇 提前

摘要:

什麽是泛型?什麽是擦除邊界?什麽是上界限定或下界限定(子類型限定或超類型限定)?什麽是類型安全?泛型extends關和super關鍵字結合通配符?使用的區別,兩種泛型在實際Android開發中有什麽用處?

一、什麽是泛型?

泛型,大概的意思是指沒有指定具體的類型的類或方法,以泛型的形式傳入一個類中或方法中,在Java編程(Android編程)裏面使用一對尖括號和一個大寫字母表示,例如:

//泛型類
public interface List<E>{}

//泛型方法,類型參數位於返回類型之前或void之前
public static <E> boolean contains(E [] arr, E x){
   for(E val:arr){
      if(x.equal(val)){
         return true;
      }
   }
   return false;
}

尖括號中的內容常用一個大寫的字母表示,但也可以是任意的單詞或同時傳入多個泛型,它的命名規則遵循類名的命名規則(首字母大寫),例如:

public interface List<AnyType,A,B,C>{}

在一個聲明了傳入泛型的類中,我們可以考慮傳入,也可以考慮不傳入,比如,現在我們需要一個列表存放數據(但沒有明確數據的類型),那麽實例化的列表,就可以存放各種類型的數據,代碼如下:

//沒有具體類型的列表
ArrayList<> mDataSet=new ArrayList<>(); 
        mDataSet.add(new Object());
        mDataSet.add(new String());
        mDataSet.add(new Integer(0));
        mDataSet.add(new Animal());

//指定具體類型的列表(存儲其他類型,編譯不通過,編譯器報錯)
ArrayList<String> mDataSetStr=new ArrayList<>();
        mDataSetStr.add(new String());

技術分享圖片

為了更好理解通配符的用法,現在我們先定幾個繼承關系的類:WhiteCuteHelloKittyCuteHelloKittyHelloKittyCatAnimal,如下圖:

技術分享圖片

通配符?既可以單獨使用,也可以結合Java關鍵字extendssuper一起使用,關於它們之間的區別在後面介紹,例如:

  //通配符單獨使用
ArrayList<?> mDataSetType=new ArrayList<>();
  //通配符結合關鍵字extends一起使用
ArrayList<? extends Animal> mAnimalChildren = new ArrayList<>();
  //通配符結合關鍵字super一起使用
ArrayList<? super HelloKitty> mAnimalParents = new ArrayList<>();

這裏,沒有列舉完泛型的用法,關於其他的一些用法可以參考其他文檔,繼續我們下面的介紹。

二、 什麽是擦除邊界?

擦除邊界,指一個具體的類型,抽象成一個泛型,那麽我們就可以在編寫代碼的時候,根據實際需要指定具體的類型,符合依賴抽象的編程思想。我們使用IDE或Eclipse開發工具編寫代碼時,在編譯期會將傳入的具體類型代替泛型,同時方便使用具體類型的屬性和方法,比如,我們在一個列表中指定存儲Animal這個具體類,那麽我們就可以方便使用Animal內部的屬性和方法,例如:

    class Animal {
        String name;

        public void setName(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }
    }

ArrayList<Animal> mAnimals = new ArrayList<>();
//指定ArrayList的泛型具體類為Animal,其對象可以存儲Animal及其子類的對象
        mAnimals.add(new Animal());
        mAnimals.add(new Cat());
        mAnimals.add(new HelloKitty());

//方便使用Animal內部的屬性和方法
        String name=mAnimals.get(0).name;
               name=mAnimals.get(1).getName();

簡單地說,擦除邊界就像一個正方形去掉了四邊,變成一個沒有固定大小沒有邊界的圖片,在以後需要使用的時候,再給它指定具體的類型,指定的類型可以是圓形、矩形、三角形或六邊形、心形等。

三、什麽是上界限定或下界限定?

在Java程序中,類與類之間最重要的一種關系——繼承關系,簡單的描述是一個縱向的排列關系(如上面Animal的UML圖)。上界,指的是往上能夠找到的最頂端的類(忽略Object類);下界,指的是外下能夠找到的最末端的類。泛型<? extends HelloKitty>表示的是所有繼承自HelloKitty的子類,那麽通配符表示的上界是HelloKitty,但我們不知道它的下界;泛型<? super CuteHelloKitty>表示的是所有CuteHelloKitty的超類,通配符?表示的下界是CuteHelloKitty,同理我們不知道它的上界。於是,初學者在看一些介紹extendssuper區別的文章時,容易將extends的上界和super的下界混到一起記憶,實際上它們彼此擁有自己的上界與下界,這是特別需要註意的!

四、什麽是類型安全?

類型安全,指的是在編碼階段,編譯器自動對代碼進行檢查,檢查變量或方法的調用是否符合當前類型,如果有不符合的情況,Java編譯器就會提示錯誤,比如,一個HelloKitty的對象mHelloKitty允許調用自身聲明或繼承的屬性和方法,這是符合類型安全的;但是,一個HelloKitty的對象mHelloKitty不允許調用子類CuteHelloKittyWhiteCuteHelloKitty聲明的屬性和方法,如果強行調用了,編譯器會提示錯誤,這是不符合類型安全的。

簡單地說,上溯造型是符合類型安全的,下溯造型是不符合類型安全的。

五、 通配符?與extends、super關鍵字使用的區別

從上面的學習中,我們知道<? extends T>表示的含義是繼承自T的一組類,在一個泛型類List<E>(或方法)中傳入泛型<? extends T>,編譯器會自動轉換成如下代碼:

//泛型類
public interface List<? extends T>{

    //泛型方法
    boolean add(? extends T e);
}

下面演示的例子,釗林將T用具體的類Animal代替,在一個泛型類ArrayList<E>傳入<? extends Animal>,然後嘗試往mAnimalChildren列表中存入子類的對象,最後查看ArrayList的add方法,如下圖:

技術分享圖片

你會發現,編譯器報錯了,不允許往一個列表中存儲Animal本身及其子類的對象,難道我理解錯誤了嗎?這到底是為什麽?

在開發者的頭腦裏,自然很容易理解AnimalCat是符合泛型<? extends Animal>規則的,但是對於編譯器來說,它只知道上面泛型表示的是一組類,但不清楚是否是具體的Animal類或其子類,在ArrayList類中,傳入的泛型,編譯器會自動將類中的泛型替換成<? extends Animal>,比如add方法,變成了boolean add(? extends Animal e),這時候試圖傳入具體的類AnimalCatHelloKittyCuteHelloKitty,編譯器會很生氣地告訴你說:“你是不是聽不懂我說的話,我要求的是? extends Animal,你卻給我一個Animal或其子類!!產生了疑惑:我應該把它當成Animal處理呢?還是應該當成HelloKitty處理呢?抑或是當成CuteHelloKitty處理呢?”因為存在類型不清楚的情況,所以編譯器禁止開發者傳入具體的類型,

這就是為什麽釗林會花一些篇幅提前介紹什麽是類型安全的原因,對於編譯器要求的是一個A類型,開發者給了一個B類型,這是不符合類型安全的!

? extends Animal表示一種類型,AnimalCatHelloKitty表示另一種類型,所以會報錯!

既然不允許我們往裏面添加數據,那麽,對於泛型? extends Animal,對於開發者到底有什麽用處?雖然編譯器不允許你往add方法傳入數據。

但是對於編譯器來說泛型類? extends Animal,可以肯定其繼承了Animal的屬性和方法,那麽在我們封裝類的時候,就可以方便地調用繼承的屬性和方法,例如:

    class Animal {
        String name;

        public void setName(String name) {
            this.name = name;
        }

        public String getName() {
            return name;
        }
    }
    class CuteHelloKitty extends HelloKitty{

        public String feature(){
            return "This is a cute HelloKitty !";
        }
    }
    /**
     * 封裝的一個類
     * @param <T>
     */
    class AnimalName<T>{
        T e;

        public T get() {
            return e;
        }

        public T print(T e) {
            return e;
        }
    }

    public void print(){
        //一、綁定為一組繼承自Animal的類,允許使用繼承的屬性和方法
        AnimalName<? extends Animal> animalName=new AnimalName();
        Animal animal=animalName.get();
        System.out.print(animal.getName());

        //二、綁定為一組CuteHelloKitty的類,允許使用繼承的屬性和方法
        AnimalName<? extends CuteHelloKitty> cuteHelloKittyName=new AnimalName();
        CuteHelloKitty cuteHelloKitty=cuteHelloKittyName.get();
        System.out.print(cuteHelloKitty.feature());
    }

既然泛型<? extends Animal>不合適往裏面添加數據,那麽開發者在設計程序的時候,對於傳入泛型<? extends Animal>的類,盡量避免調用往寫入數據的方法,只調用讀取數據的方法。泛型類<? extends Animal>表示以Animal為上界的所有子類,不確定具體的類型,可能會下溯造型,不符合類型安全!!

但是,如果我想要調用寫入數據的方法呢,那該怎麽辦?那麽,你可以考慮使用泛型<? super T>,該泛型表示的含義是以T為下界的一組類,如下圖:

技術分享圖片

看一下下面的例子,編譯器允許開發者這樣子操作,代碼如下:

        //三、綁定為以CuteHelloKitty為下界的一組類
        AnimalName<? super CuteHelloKitty> helloKitty = new AnimalName<>();

        helloKitty.print(new CuteHelloKitty());
        helloKitty.print(new WhiteCuteHelloKitty());

技術分享圖片

類中傳入泛型<? super T>,對於傳入的CuteHelloKitty類本身及其子類,在這裏編譯器會自動上溯造型為CuteHelloKitty,上溯造型符合類型安全的,因此可以調用寫入數據的print方法)。編譯器允許傳入的是T自身或T子類的對象,最終上溯造型為T,符合類型安全!!

總結:

在一個封裝類中傳入泛型,可以在編碼的時候指定泛型為某個具體的類,也可以指定為一組類,指定為一組類可以考慮使用通配符?extendssuper關鍵字結合,泛型類<? extends T>表示以T為上界的一組子類,適合讀取數據,不建議調用寫入數據的方法,否則編譯器會報錯;泛型<? super T>表示以T為下界的一組超類,適合調用寫入數據的方法,不適合讀取數據。

在開發中,根據實際的需要合理選擇傳入<? extends T><? super T>或者不使用通配符,符合類型安全!!

Android開發之深入理解泛型extends和super的區別