擦除
java泛型是使用擦除來實現的
原始類型 就是刪去類型參數後的泛型類型名。
擦除類型變量,並替換爲限定類型(邊界類型)
若無顯示的邊界 則默認以類型Object爲邊界
在泛型代碼內部 無法獲取任何有關泛型類型的信息,
可以知道類型參數的標識符 和 泛型類型邊界,卻無法知道用來創建特定實例的實際類型參數
使用Class<T> 是獲取運行時所需的類型信息的唯一的方式 讓用戶顯示的傳入一個Class引用 通常是作爲一個方法中的參數傳入 之後 使用反射來顯示地引入該類並創建實例
- 不能用基本類型實例化類型參數 如: ArrayList<int>
- 不能用instanceof中操作測試泛型類型 a instanceof pair<String> //Error instanceof 語法規則 : A instanceof B ;//A必須可以被解析爲解析爲變量 比如常量 對象 局部變量 // B必須可以被解析爲類型,比如類 接口 枚舉
- 不能創建參數化類型的數組
- Pair <String> [] table = new Pair<String>[10];
-
可以這樣創建 Pair <String> [] table = new Pair[10]; 或者 Pair <String> [] table = (Pair<String>[]) new Pair<?>[10]; 比較推薦的方法 ArrayList<Pair<String>>
- 實際上可以向方法傳入不定數參數
public void p(T...a){ for (T c:a) { System.out.println(c.toString()); } }
4.不能實例化類型變量 不能這樣使用 :new T(..) , new T[..] 或T.class
解決:在泛型類中構造一個 方法提供 -
// 調用 讓用戶提供一個構造器表達式 Pair.makePair(String::new) public static <T> Pair<T> makePair (Supplier<T> constr){ try{return new Pair<>(constr.get(),constr.get());} catch (Exception e){return null;} } //調用 Pair.makePair(String.class) public static <T> Pair<T> makePair(Class<T> c1){ try{ return new Pair<>(c1.newInstance(),c1.newInstance()); } catch(Exception e) {return null;} }
5.不能構造泛型數組
如public static <T extends Comparable> T[] minmax(T[] a){ T[] mm=new T[2];return a;}//Error 不能創建泛型數組 -
import java.lang.reflect.Array; import java.util.function.IntFunction; public static <T extends Comparable> T[] minmax(IntFunction<T[]> constr,T...a){ T[] mm=constr.apply(2);...} public static <T extends Comparable> T[] minmax(IntFunction<T[]> constr,T...a){ T[] mm=(T[])Array.newInstance(a.getClass().getComponentType(),2);...}
6.泛型類的靜態上下文中類型變量無效,即不能在靜態域中使用類型變量,但static可以修飾泛型方法
7.不能拋出或捕獲泛型類的實例(),泛型類擴展Throwable都是不合法的
泛型類不能擴展Throwablepublic static <T extends Throwable> void doWork(Class<T> t){ try{ do work} catch(T e) { Logger.global.info(...)} }
List<String> v = new ArrayList<String>();
System.out.println(v instanceof List<String>);
//編譯器報錯:不能對參數化類型列表執行instanceof檢查
//使用 List<?> 代替 因爲進一步的泛型類型信息將在運行時被刪除
//就是說可以改爲 v instanceof List<?>
不能讓類實現兩個看上去不同的泛型接口
public abstract class Test implements List<String> , List<Date>{ }
//編譯器報錯:不能使用不同的參數 List<Date> 和 List<String> 來多次實現接口 List
//原因由於泛型是使用擦除來實現的 它們在運行時是相同的
原始類型:每個泛型都有一個原始類型 比如List<String> List<Integer> ,List就是原始類型
參數化類型 與 原始類型 的相互賦值
//原始類型 泛型類型
List list = new ArrayList<Date>();//警告 List 是原始類型。應該將對通用類型 List<E> 的引用參數化
//泛型類型 原始類型
List<Date> dates = new ArrayList();//未受檢警告 類型安全:類型 ArrayList 的表達式需要進行未經檢查的轉換以符合
//不允許 明顯不兼容的內容賦值
List<Date> dates = new ArrayList<String>();
//報錯 類型不匹配:不能從 ArrayList<String> 轉換爲List<Date>
泛型與繼承
繼承只適用於"基本"泛型類型,而不適合參數類型
僅有兩個泛型類型都是基於完全相同的參數類型進行實例化的時候 賦值才適用
//前提 1.List接口是COllection接口的子類
// 2.List<x> Collocation<y> x完全等於y
Collection<Date> cd;
List<Date> ld = new ArrayList<Date>();
cd = ld ; //ok
Collection<Date> cd = new ArrayList<Date>();
List<Date> ld ;
// ld=cd ; //編譯器報錯 但可以強制類型轉換
ld=(List<Date>) cd ; //ok
Collection<Object> cd;
List<Date> ld = new ArrayList<Date>();
cd = ld ;//Error 類型不匹配
將數組引用類型與實例類型不匹配造成的運行時錯誤移到編譯期
class Fruit { }
class Apple extends Fruit {} // Fruit
class Jonathan extends Apple { } // Apple //Orange
class Orange extends Fruit { } // Jonathan
public class CovarianArrays {
public static void main(String [] args){
Fruit [] fruit = new Apple [10];
fruit[0] = new Apple();
fruit[1] = new Jonathan();
System.out.println(fruit.getClass().getName()); //[LApple;
try{
fruit[0] = new Fruit();
}catch(Exception e) {
System.out.println(e);//ArrayStoreException
}
try{
fruit[0] = new Orange();
}catch(Exception e) {
System.out.println(e);//ArrayStoreException
}
}
}
將Fruit數組的引用指向 Apple數組的實例
編譯期 因爲實際上實例是Apple[] 所以可以放入Apple Jonathan
又因爲因爲引用類型爲Fruit類型所以 可以接受Fruit Orange
但運行時的數組機制知道其類型爲Apple[] 所以不允許放入Fruit 和Orange類型
使用泛型將這種錯誤檢查移入編譯期
List<Fruit> flist = new ArrayList<Apple>();
//前面提到過 編譯器會報錯,不允許這種情況
泛型數組也存在類似的引用類型"欺騙編譯器的情況"
Collection<Date> cd = new TreeSet<Date>();
List<Date> ld ;
ld=(List<Date>) cd ;
//運行錯誤 Exception in thread "main" java.lang.ClassCastException: java.util.TreeSet cannot be cast to java.util.List
數組類型的通配符
-
數組必須是無界限通配符類型 因爲數組的無界限通配符實例化中的每一個元素都可以保存任何類型 在運行時 並不一定要對類型的泛型部分進行運行檢查 , ArrayList的任何實例對於ArrayList<?>都是可以賦值的
-
因此只有原始類型檢查是必須的
來看一下Enum的聲明
Enum <E extends Enum<E> >{..} //java不允許我們擴展Enum類型
class Foo e xtends Enum<Foo> 1.滿足Enum只能有自己的子類來實例化
2.引用了類型變量E的父類Enum類的任何方法 現在都必須引用子類型
E是某個參數化的Enum的一個子類