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;
}