1. 程式人生 > >《瘋狂Java講義》學習筆記(八)泛型

《瘋狂Java講義》學習筆記(八)泛型

1、概述

  • 增加泛型其中一個重要原因是為了讓集合能記住其元素的資料型別,防止從集合取出物件時,強轉型別容易引起ClassCastExeception異常
  • 泛型將執行時異常轉移至編譯時異常:
List list = new ArrayList();
list.add("123"); 
list.add(123); //執行時會異常
List<String> list = new ArrayList<String>();
list.add("123"); 
list.add(123); //編譯時會異常
  • Java7可以寫成:
List<String>
list = new ArrayList<>();
  • 泛型的實質:允許在定義介面、類時宣告型別形參,型別形參在整個介面、類體內可當成型別使用

2、從泛型派生子類

  • 當建立了帶泛型宣告的介面、父類之後,可以為該介面建立實現類,或從該父類派生子類,當使用這些介面、父類時不能再包含型別形參,下面是錯誤的:
public class A extends Apple<T>{}
  • 定義方法時可以宣告資料形參,呼叫方法時必須為這些資料形參傳入實際的資料;而定義類、介面、方法時可以宣告型別形參,使用類、介面、方法時應該為型別形參傳入實際的型別

public class A extends Apple{}

  • 也可以不為型別形參傳入實際的型別引數
public class A extends Apple{}
  • 如果從Apple類派生子類,則在Apple類中所有使用T型別形參的地方都將被替換成String型別,重寫父類的方法,就必須注意這一點

3、並不存在泛型類

  • ArrayList類像一種特殊的ArrayList類,但實際上,系統並沒有為ArrayList生成新的class檔案,也不會把它當作新類來處理
  • 不管泛型的實際型別引數是什麼,它們在執行時總有同樣的類,都會被當成同一個類處理,在記憶體中也只佔用一塊記憶體空間,因此在靜態方法、靜態初始化塊活著靜態變數的宣告和初始化中不允許使用型別形參
錯誤示範:
public class R<t>{
    static T info;
    T age;
    public void foo(T msg){}
    public static void bar(T msg){}
}
  • 由於系統中並不會真正生成泛型類,所以instanceof運算子後不能使用泛型類,錯誤示範:
List<String> list = new ArrayList<String>();
if(list instanceof java.util.ArrayList<String>){...}

4、型別萬用字元

  • Java早期設計中,允許Integer[]陣列賦值給Number[]變數存在缺陷,因此在泛型設計時進行了改進,不再允許把List物件賦值給List變數
  • 型別萬用字元是一個問號(?),將一個問號作為型別實參傳給List集合,寫作List
public void test(List<?> c){
    for(int i=0;i<c.size();i++){
        System.out.println(c.get(i))
    }
}
  • 帶萬用字元的List僅表示它是各種泛型List的父類,並不能把元素加入到其中,錯誤示範:
List<?> c = new ArrayList<String>();
// 下面會引起編譯時錯誤
c.add(new Object());
但null是例外,它是所有引用型別的例項
  • 萬用字元的上限:如果只希望它代表某一類泛型List的父類,可以寫成
public class Canvas{
    public void drawAll(List<? extends Sharp> sharps){
        for(Shape s:shapes){
            s.draw(this);
        }
    }
}
// 但以下是錯誤的,因為僅僅知道是Sharp的子類,並沒有指明具體是哪個子類
public void addRectangle(List<? extends Sharp> sharpes){
    sharpes.add(new Rectangle());
}
  • 設定型別形參的上限
public class Apple<T extends Number>{
    T col;
}
  • 多重形參上限
public class Apple<T extends Number && java.io.Serializable>{...}

5、泛型方法

static <T> void arrayToCollection(T[]a, Collection<T> c){
    for(T o:a){
        c.add(o);
    }
}
  • 上面的方法無法使用萬用字元,因為Java不允許把物件放進一個未知型別的集合中
  • 而上面的方法統一了陣列和集合的型別,所以可以
  • 與介面、類宣告中定義的型別形參不同,方法宣告中定義的形參只能在該方法中使用
  • 泛型方法中使用萬用字元
static <T> void test(Collection<? extends T> from,Collection<T> to){
    for(T ele:from){
        to.add(ele);
    }
} 
public static main(String[] args){
    test(new ArrayList<Object>(),new ArrayList<String>());
}

6、泛型方法和型別萬用字元的區別

  • 大多數時候可以使用泛型方法來替代型別萬用字元:
public interface Collection<E>{
    boolean containsAll(Collection<?> c);
    boolean addAll(Collection<? extends E> c);
}
public interface Collection<E>{
    <T> boolean containsAll(Collection<T> c);
    <T extends E> boolean addAll(Collection<T> c);
}
  • 如果沒有形參之間的依賴關係或者方法返回值與引數之間的型別沒有依賴關係,則不應該使用泛型,以下情況需要使用:
public class Collections{
    public static <T , S extends T> void copy(List<T> dest,List<S> src){...}
}

7、萬用字元下限

  • 錯誤示範:
public static <T> T copy(Collection<T> dest,Collection<? extends T> src){
    T last = null;
    for(T ele:src){
        last = ele;
        dest.add(ele);
    }
    return last;
}
// 問題在於src的型別不確定,當呼叫的時候由於引數型別不同會報錯:
copy(new ArrayList<Number>(),new ArrayList<Integer>())
  • 正確示範
public static <T> T copy(Collection<? super T> dest,Collection<T> src){
    T last = null;
    for(T ele:src){
        last = ele;
        dest.add(ele);
    }
    return last;
}