---------------------- ASP.Net+Android+IOS開發、.Net培訓、期待與您交流! ----------------------
泛型2:
Jdk 1.5以前的集合類中存在什麼問題:
ArrayList collection = new ArrayList();
collection.add(8);
collection.add(8L);
collection.add("abc");
int i = (Integer) collection.get(8); //編譯要強制類型轉換且運行時出錯!
Jdk 1.5的集合類希望你在定義集合時,明確表示你要向集合中裝哪種類型的數據,無法加入指定類型以外的數據。
ArrayList<Integer> collection2 = new ArrayList<Integer>();
collection2.add(8);
/*collection2.add(8L);
collection2.add(“abc”);*/ //這兩行代碼編譯時就報告了語法錯誤
int i2 = collection2.get(0); //不需要再進行類型轉換
泛型是提供給javac編譯器使用的,可以限定集合中的輸入類型,讓編譯器擋住源程序中的非法輸入,編譯器編譯帶類型說明的集合時會去除 掉“類型”信息,使程序運行效率不受影響,對於參數化的泛型類型,getClass()方法的返回值和原始類型完全一樣。由於編譯生成的字節碼會 去掉泛型的類型信息,只要能跳過編譯器,就可以往某個泛型集合中加入其它類型的數據,例如,用反射得到集合,再調用其add方法即 可。
ArrayList<E>類定義和ArrayList<Integer>類引用中涉及如下術語:
整個稱爲ArrayList<E>泛型類型
ArrayList<E>中的E稱爲類型變量或類型參數
整個ArrayList<Integer>稱爲參數化的類型
ArrayList<Integer>中的Integer稱爲類型參數的實例或實際類型參數
ArrayList<Integer>中的<>念着typeof
ArrayList稱爲原始類型
參數化類型與原始類型的兼容性:
參數化類型可以引用一個原始類型的對象,編譯報告警告,例如:
Collection<String> c = new Vector(); //可不可以,不就是編譯器一句話的事嗎?
原始類型可以引用一個參數化類型的對象,編譯報告警告,例如:
Collection c = new Vector<String>(); //原來的方法接受一個集合參數,新的類型也要能傳進去
參數化類型不考慮類型參數的繼承關係:
Vector<String> v = new Vector<Object>(); //錯誤!不寫<Object>沒錯,寫了就是明知故犯
Vector<Object> v = new Vector<String>(); //錯誤!
編譯器不允許創建泛型變量的數組。即在創建數組實例時,數組的元素不能使用參數化的類型,如下面語句有錯誤:
Vector<Integer> vectorList[] = new Vector<Integer>[10];
思考題:下面的代碼會報錯誤嗎?
Vector v1 = new Vector<String>(); //不會
Vector<Object> v = v1; //不會,編譯器只看本行代碼有錯誤嗎?沒有就通過。
問題:定義一個方法,該方法用於打印出任意參數化類型的集合中的所有數據,該方法如何定義呢?
錯誤方式:
public static void printCollection(Collection<Object> col) {
for(Object obj:col) {
System.out.println(obj);
}
/* col.add("string"); //沒錯
col = new HashSet<Date>(); //會報告錯誤*/
}
正確方式:
public static void printCollection(Collection<?> col) {
for(Object obj:col) {
System.out.println(obj);
}
//col.add("string"); //錯誤,因爲它不知自己未來匹配就一定是String
col.size(); //沒錯,此方法與類型參數沒有關係
col = new HashSet<Date>();
}
總結:
使用?通配符可以引用其他各種參數化的類型,?通配符定義的變量主要用作引用,可以調用與參數化無關的方法,不能調用與參數化有 關的方法。
泛型中的“?”通配符擴展:
限定通配符的上邊界:
正確:Vector<? extends Number> x = new Vector<Integer>();
錯誤:Vector<? extends Number> x = new Vector<String>();
限定通配符的下邊界:
正確:Vector<? super Integer> x = new Vector<Number>();
錯誤:Vector<? super Integer> x = new Vector<Byte>();
提示:限定通配符總是包括自己。?只能用作引用,不能用它去給其他變量賦值。
Vector<? extends Number> y = new Vector<Integer>();
Vector<Number> x = y;
上面的代碼錯誤,原理與Vector<Object > x11 = new Vector<String>();相似,只能通過強制類型轉換方式來賦值。
能寫出下面的代碼即代表掌握了Java的泛型集合類:
HashMap<String,Integer> hm = new HashMap<String,Integer>();
hm.put("zx",21);
hm.put("ls",22);
Set<Map.Entry<String,Integer>> mes= hm.entrySet();
for(Map.Entry<String,Integer> me : mes) {
System.out.println(me.getKey() + ":" + me.getValue());
}
對在jsp頁面中也經常要對Set或Map集合進行迭代:
<c:forEach items=“${map}” var=“entry”>
{entry.key}:${entry.value}
</c:forEach>
定義泛型的方法:
Java的泛型方法沒有C++模板函數功能強大,java中的如下代碼無法通過編譯:
<T> T add(T x,T y) {
return (T) (x+y);
//return null;
}
用於放置泛型的類型參數的尖括號應出現在方法的其他所有修飾符之後和在方法的返回類型之前,也就是緊鄰返回值之前。按照慣例,類型參數 通常用單個大寫字母表示。
交換數組中的兩個元素的位置的泛型方法語法定義如下:
static <E> void swap(E[] a, int i, int j) {
E t = a[i];
a[i] = a[j];
a[j] = t; //或用一個面試題講:把一個數組中的元素的順序顛倒一下
}
只有引用類型才能作爲泛型方法的實際參數,swap(new int[3],3,5);語句會報告編譯錯誤。
除了在應用泛型時可以使用extends限定符,在定義泛型時也可以使用extends限定符,如:Class.getAnnotation()方法的定義。並且可以用&來 指定多個邊界,如<V extends Serializable & cloneable> void method(){}
普通方法、構造方法和靜態方法中都可以使用泛型。
也可以用類型變量表示異常,稱爲參數化的異常,可以用於方法的throws列表中,但是不能用於catch子句中。
在泛型中可以同時有多個類型參數,在定義它們的尖括號中用逗號分,如:
public static <K,V> V getValue(K key) { return map.get(key);}
只有引用類型才能作爲泛型方法的實際參數,對於add方法,使用基本類型的數據進行測試沒有問題,這是因爲自動裝箱和拆箱了。 swap(new int[3],3.5);語句會報告編譯錯誤,這是因爲編譯器不會對new int[3]中的int自動拆箱和裝箱了,因爲new int[3]本身已經是對象了,你想 要的有可能就是int數組呢?它裝箱豈不弄巧成拙了。
用下面的代碼說明對異常如何採用泛型:
private static <T extends Exception> sayHello() throws T{
try{
}catch(Exception e){
throw (T e);
}
}
泛型參數的類型推斷:
編譯器判斷範型方法的實際類型參數的過程稱爲類型推斷,類型推斷是相對於知覺推斷的,其實現方法是一種非常複雜的過程。
根據調用泛型方法時實際傳遞的參數類型或返回值的類型來推斷,具體規則如下:
當某個類型變量只在整個參數列表中的所有參數和返回值中的一處被應用了,那麼根據調用方法時該處的實際應用類型來確定,這很容易憑 着感覺推斷出來,即直接根據調用方法時傳遞的參數類型或返回值來決定泛型參數的類型,如:swap(new String[3],3,4) à static <E> void swap(E[] a, int i, int j),當某個類型變量在整個參數列表中的所有參數和返回值中的多處被應用了,如果調用方法時這多處的實際應用類 型都對應同一種類型來確定,這很容易憑着感覺推斷出來,如:add(3,5) à static <T> T add(T a, T b)
當某個類型變量在整個參數列表中的所有參數和返回值中的多處被應用了,如果調用方法時這多處的實際應用類型對應到了不同的類型,且 沒有使用返回值,這時候取多個參數中的最大交集類型,例如,下面語句實際對應的類型就是Number了,編譯沒問題,只是運行時出問 題:fill(new Integer[3],3.5f) à static <T> void fill(T[] a, T v),當某個類型變量在整個參數列表中的所有參數和返回值中的多處被應用了,如 果調用方法時這多處的實際應用類型對應到了不同的類型,並且使用返回值,這時候優先考慮返回值的類型,例如,下面語句實際對應的類 型就是Integer了,編譯將報告錯誤,將變量x的類型改爲float,對比eclipse報告的錯誤提示,接着再將變量x類型改爲Number,則沒有了錯 誤:int x =(3,3.5f) à static <T> T add(T a, T b),參數類型的類型推斷具有傳遞性,下面第一種情況推斷實際參數類型爲Object,編譯沒有 問題,而第二種情況則根據參數化的Vector類實例將類型變量直接確定爲String類型,編譯將出現問題:copy(new Integer[5],new String[5]) à static <T> void copy(T[] a,T[] b);copy(new Vector<String>(), new Integer[5]) à static <T> void copy(Collection<T> a , T[] b);
如果類的實例對象中的多處都要用到同一個泛型參數,即這些地方引用的泛型類型要保持同一個實際類型時,這時候就要採用泛型類型的方 式進行定義,也就是類級別的泛型,語法格式如下:
public class GenericDao<T> {
private T field1;
public void save(T obj){}
public T getById(int id){}
}
類級別的泛型是根據引用該類名時指定的類型信息來參數化類型變量的,例如,如下兩種方式都可以:
GenericDao<String> dao = null;
new genericDao<String>();
注意:在對泛型類型進行參數化時,類型參數的實例必須是引用類型,不能是基本類型。
當一個變量被聲明爲泛型時,只能被實例變量、方法和內部類調用,而不能被靜態變量和靜態方法調用。因爲靜態成員是被所有參數 化的類所共享的,所以靜態成員不應該有類級別的類型參數。
---------------------- ASP.Net+Android+IOS開發、.Net培訓、期待與您交流! ----------------------