一、體驗泛型
JDK1.5之前的集合類中存在的問題——可以往集合中加入任意類型的對象,例如下面代碼:
1 package cn.gacl.generic.summary; 2 3 import java.util.ArrayList; 4 5 public class GenericTest { 6 7 public static void main(String[] args) { 8 /** 9 * 不使用泛型之前ArrayList容器可以存儲任意類型的對象 10 */ 11 ArrayList collection1 = new ArrayList(); 12 collection1.add(1);//存儲Integer對象 13 collection1.add(1L);//存儲Long對象 14 collection1.add("xdp");//存儲String對象 15 /** 16 * 這里會報異常: JAVA.LANG.CLASSCASTEXCEPTION: 17 * JAVA.LANG.LONG CANNOT BE CAST TO JAVA.LANG.INTEGER 18 * 19 */ 20 int i = (Integer) collection1.get(1); 21 } 22 }
JDK1.5之后的集合類希望你在定義集合時,明確表示你要向集合中裝哪種類型的數據,無法加入指定類型之外的數據,例如下面的代碼:
/** * 使用泛型限定ArrayList容器只能存儲字符串類型的對象 */ ArrayList<String> collection2 = new ArrayList<String>(); collection2.add("孤傲蒼狼"); //collection2.add(1);//報錯,因為限制了collection2只能存儲String類的對象,不能加入Integer類型的對象 //collection2.add(1L);//報錯,因為限制了collection2只能存儲String類的對象,不能加入Long類型的對象 //由於已經指明集合中存儲的是字符串類型的對象,因此這里不用再強制轉型了 String element = collection2.get(0);
泛型是提供給Javac編譯器看的,可以限定集合中的輸入類型,讓編譯器擋住源程序中的非法輸入,編譯器編譯帶參數類型說明的集合時會去去除掉“類型”信息,使程序運行不受影響,對於參數化的泛型類型,getClass()方法的返回值和原始類型完全一樣,由於編譯生成的字節碼會去掉泛型的類型信息,因此只要能跳過編譯器,就可以往某個泛型集合中加入其他類型的數據。
例如下面的代碼就演示了"使用反射得到集合,然后調用add方法往原本只能存儲Integer對象的集合中存儲一個String類型的對象"
1 ArrayList<Integer> collection3 = new ArrayList<Integer>(); 2 //對於參數化的泛型類型,getClass()方法的返回值和原始類型完全一樣 3 System.out.println(collection3.getClass());//結果為:java.util.ArrayList 4 System.out.println(collection3.getClass() == collection2.getClass());//結果為true 5 //使用反射得到集合,然后調用add方法往原本只能存儲Integer對象的集合中存儲一個String類型的對象 6 collection3.getClass().getMethod("add", Object.class).invoke(collection3, "abc"); 7 System.out.println(collection3.get(0));//輸出的結果為:abc,這證明字符串對象確實是存儲到了原本只能存儲Integer對象的集合中
備注:
- 泛型是JDK1.5的所有新特性中最難深入掌握的部分,沒有使用泛型時,只要是對象,不管是什么類型的對象,都可以存儲進同一個集合中,使用泛型集合,可以將一個集合中的元素限定為一個特定類型,這樣集合中就只能存儲同一類型的對象,這樣更安全;並且當從集合中獲取一個對象時,編譯器也知道這個對象的類型,不需要對對象進行強制類型轉換,這樣更方便。
- 在JDK1.5之后,你還可以按原來的方式將各種不同類型的數據放到同一個集合中,但是編譯時會報一個unChecked警告
- 泛型中的類型參數嚴格說明集合中裝載的數據類型是什么和可以加入什么類型的數據,記住:Collection<String>和Collectin<Object>是兩個沒有轉換關系的參數化的類型
二、了解泛型
- 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>();//錯誤,語法上不通過
- Vector<Object> v = new Vector<String>();//錯誤,語法上不通過
假設Vector<String> v = new Vector<Object>;可以的話,那么以后從v中取出的對象當作String用,而v實際指向的集合中可以加入任意類型的對象,
假設Vector< Object > v = new Vector< String >;可以的話,那么以后可以向v中加入任意類型的對象,而v實際指向的集合中只能裝String類型的對象
思考:下面的代碼會報錯嗎?(不會報錯)
- Vector v1 = new Vector<String>();//參數化類型的對象可以給原始類型的引用
- Vector<Object> v=v1;//參數化類型的引用可以指向原始類型的對象
三、泛型中的?通配符
問題:定義一個方法,該方法可以打印出任意參數化類型的集合中的所有數據,該方法如何定義呢?
錯誤的定義:
1 /** 2 * Collection<Object>中的Object只是說明Collection<Object>實例對象中的方法接收的參數是Object 3 * Collection<Object>是一種具體的類型,new HashSet<Date>也是一種具體的類型,兩者沒有兼容性問題 4 * @param collection 5 */ 6 public static void printCollection(Collection<Object> collection){ 7 for(Object obj:collection){ 8 System.out.println(obj); 9 } 10 collection.add("abc");//沒錯 11 collection=new HashSet<Date>();//會報告錯誤 12 }
正確的定義:
1 /**這里的Collection<?>中的?表示可以傳人任意的類型參數 2 * Collection<?> cols可以匹配任意參數化的類型,但是到底匹配的是什么類型,只有以后才知道 3 * 所以 cols=new ArrayList<Integer>和cols = new ArrayList<String>都可以 4 * 但是cols.add("abc")或cols.add(new Date())都不行 5 */ 6 public static void printCollection(Collection<?> collection){ 7 for(Object obj:collection){ 8 System.out.println(obj); 9 } 10 //collection.add("abc");//報錯,因為collection不知道未來匹配的一定是String類型 11 collection.size();//不報錯,此方法與參數類型沒有關系 12 collection=new HashSet<Date>();//這是可以的 13 }
總結:使用?通配符可以引用其他各種參數化的類型,?通配符定義的變量主要用作引用,可以調用與參數無關的方法,不能調用與參數有關的方法
四、泛型中的?通配符的擴展
1.限定通配符?的上邊界
- 正確的寫法:Vector<? extends Number> x = new Vector<Integer>();
這里指的是?所代表的參數化類型必須是繼承Number類的,如這里的?所代表的Integer類型就是繼承Number類的
- 錯誤的寫法:Vector<? extends Number> x = new Vector<String>();
2.限定通配符?的下邊界
- 正確的寫法:Vector<? super Integer> y = new Vector<Number>();
這里指的是?所代表的參數化類型必須是Integer類的父類,如這里的?所代表的Number類型就是Integer類的父類
- 錯誤的寫法:Vector<? super Integer> y = new Vector<Byte>();
五、泛型的綜合應用
1 package cn.itcast.day2; 2 import java.util.HashMap; 3 import java.util.HashSet; 4 import java.util.Map; 5 import java.util.Set; 6 /** 7 * 此類是用來演示泛型的應用的 8 * 9 * @author 孤傲蒼狼 10 * 11 */ 12 public class GenericCase { 13 public static void main(String[] args) { 14 HashMap<String, Integer> maps = new HashMap<String, Integer>(); 15 maps.put("lhm", 35); 16 maps.put("flx", 33); 17 /** 18 * 變量的命名技巧:如果以后不知道一個變量該如何命名,就可以以方法名的形式來命名, 19 * 如果要定義變量接收返回值,如果此時不知道如何定義變量名時,就直接定義成returnValue 20 */ 21 Set<Map.Entry<String, Integer>> entrySet = maps.entrySet();// 這里的變量名直接以方法名的形式定義 22 // 使用增強的for循環迭代Map容器中的key和value 23 //這里的Entry是Map類的一個內部類,map類中存儲的key和value都是封裝在這個Entry內部類中的 24 //這個Entry內部類提供了getKey方法取出鍵,getValue方法取出值 25 for (Map.Entry<String, Integer> entry : entrySet) { 26 System.out.println(entry.getKey() + ":" + entry.getValue()); 27 } 28 } 29 }
在JSP頁面中也經常要使用迭代標簽<c:forEach>對Set或Map集合進行迭代:
1 <c:forEach items=”${map}” var=”entry”> 2 ${entry.key}:${entry.value} 3 </c:forEach>
六、自定義泛型方法
1 package cn.itcast.day2; 2 import java.io.Serializable; 3 /** 4 * 此類是用來演示如何定義和使用泛型方法的 5 * 6 * @author 孤傲蒼狼 7 * 8 */ 9 public class GenericMethod { 10 public static void main(String[] args) { 11 add(3, 5); 12 Number x1 = add(3.5, 5);// Integer類型和Double類型的交集就是Number類,Number類是這兩個類的父類,所以可以定義Number類型的變量來接收返回值 13 Object x2 = add(3, "abc");// Integer類型和String類型的交集就是Object類,因為Object類是所有類的父類,所以可以定義Object類型的變量來接收返回值 14 /** 15 * swap(new String[]{"abc","123","xdp"},1,2);的執行結果如下: 16 * abc 123 xdp 17 * abc xdp 123 18 * 從結果來看,索引為1的元素和索引為2的元素的確是交換了位置 19 */ 20 swap(new String[] { "abc", "123", "xdp" }, 1, 2);// 調用自定義泛型方法,傳入的實際參數必須是引用類型的數組 21 // swap(new int[]{1,2,3,4,5},1,3);//只有引用類型才能作為泛型方法的實際參數,這里的int[]數組是屬於基本類型,不能作為泛型方法的參數,所以這樣會報錯 22 printArray(new Integer[]{1,2,3});//可以傳入Integer類型的數組,因為Integer類型的數組是屬於引用類型的 23 //printArray(new int[]{10,2,5});不能傳入非引用類型的數組作為泛型方法的實際參數 24 } 25 /** 26 * 泛型方法的定義語法: 這里定義的就是一個泛型方法 方法的返回值類型是T,即任意的類型 返回值的具體類型由傳入的類型參數來定 27 * 28 * @param <T> 29 * 代表任意的類型 30 * @param x 31 * @param y 32 * @return 33 */ 34 private static <T> T add(T x, T y) { 35 return null; 36 } 37 /** 38 * 定義一個泛型方法來交換數組中指定索引位置的兩個元素 這里傳入的數組可以是任意類型的數組 39 * 傳入泛型方法的實際參數類型必須是引用類型的數組,不能是基本類型的數組 40 * 41 * @param <T> 42 * @param a 43 * @param i 44 * @param j 45 */ 46 private static <T> void swap(T[] a, int i, int j) { 47 // 數組中元素位置未交換前的打印結果 48 printArray(a); 49 T temp = a[i]; 50 a[i] = a[j]; 51 a[j] = temp; 52 System.out.println(); 53 // 數組中元素位置交換后的打印結果 54 printArray(a); 55 } 56 /** 57 * 定義打印任意引用數組類型的方法 58 * 59 * @param <T> 60 * @param array 61 */ 62 private static <T> void printArray(T[] array) { 63 for (T t : array) { 64 System.out.print(t + "\t"); 65 } 66 } 67 /** 68 * 定義有extends限定符,並且具有多個上邊界的泛型方法,各個邊界使用&符號分隔 69 * @param <T> 70 */ 71 public <T extends Serializable & Cloneable> void method(){} 72 }
普通方法,構造方法和靜態方法都可以使用泛型
七、泛型方法練習題
- 編寫一個泛型方法,自動將Object類型對象轉換為其他類型
- 定義一個泛型方法,可以將任意類型的數組中的所有元素填充為相應類型的某個對象
- 采用自定泛型方法的方式打印出任意參數化類型的集合中的所有內容。
- 定義一個泛型方法,把任意參數類型的集合中的數據安全地復制到相應類型的數組中
- 定義一個泛型方法,把任意參數類型的一個數組中的數據安全地復制到相應類型的另一個數組中去
1 /** 2 * 1.編寫一個泛型方法,自動將Object類型對象轉換為其他類型 3 * @param <T> 4 * @param obj 5 * @return 6 */ 7 private static <T> T autoConvert(Object obj){ 8 return (T)obj; 9 } 10 /** 11 * 2.定義一個泛型方法,可以將任意類型的數組中的所有元素填充為相應類型的某個對象 12 * @param <T> 13 * @param array 14 * @param obj 15 */ 16 private static <T> void fillArray(T[] array,T obj){ 17 for(int i=0;i<array.length;i++){ 18 array[i]=obj; 19 } 20 printArray(array); 21 } 22 /** 23 * 3.采用自定泛型方法的方式打印出任意參數化類型的集合中的所有內容 24 * @param <T> 25 * @param collection 26 */ 27 private static <T> void printCollection(Collection<T> collection){ 28 System.out.println(collection.size()); 29 for(Object obj:collection){ 30 System.out.println(obj); 31 } 32 } 33 /** 34 * 4.定義一個泛型方法,把任意參數類型的集合中的數據安全地復制到相應類型的數組中 35 * @param <T> 36 * @param srcCollection 37 * @param descArray 38 */ 39 private static <T> void CollectionCopyToarray(Collection<T> srcCollection,T[] descArray){ 40 Iterator<T> it = srcCollection.iterator(); 41 int recordElementPostion=0; 42 while(it.hasNext()){ 43 descArray[recordElementPostion]=it.next(); 44 recordElementPostion++; 45 } 46 printArray(descArray); 47 } 48 /** 49 * 5.定義一個泛型方法,把任意參數類型的一個數組中的數據安全地復制到相應類型的另一個數組中去 50 * @param <T> 51 * @param srcArray 52 * @param descArray 53 */ 54 private static <T> void srcArrayToDescArray(T[] srcArray,T[] descArray){ 55 for(int i=0;i<srcArray.length;i++){ 56 descArray[i]=srcArray[i]; 57 } 58 printArray(descArray); 59 } 60 private static <T> void printArray(T[] array) { 61 for (T t : array) { 62 System.out.print(t + "\t"); 63 } 64 }
八、自定義泛型類
如果類的實例對象中有多處都要用到同一個泛型參數,即這些地方引用的泛型類型要保持同一個實際類型時,這時就要采用泛型類型的方式定義,也就是類級別的泛型,語法格式如下:
1 package cn.itcast.day2; 2 import java.util.Set; 3 import cn.itcast.day1.ReflectField; 4 /** 5 * DAO:Data Access Object(數據訪問對象) 6 * 數據訪問:CRUD,即增刪改查 7 * @author 孤傲蒼狼 8 * 此類是用來演示如何定義泛型類 9 * 此泛型類中的<E>中的E代表實際操作的類型 10 * 指明了操作類型E之后,GenericDAO類中定義的CRUD方法就都是針對於指定的類型 11 */ 12 public class GenericDAO<E> { 13 private E field1; //定義泛型類型的成員變量 14 public <E> void add(E x){ 15 } 16 public <E> E findById(int id){ 17 return null; 18 } 19 public void delete(E obj){ 20 } 21 public void delete(int id){ 22 } 23 public void update(E obj){ 24 } 25 //public static void update(E obj){}這樣定義會報錯,靜態方法不允許使用泛型參數 26 public static<E> void update2(E obj){}//這樣定義就可以,此時的這個靜態方法所針對的類型和GenericDAO<E>中指定的類型是兩個不同的類型 27 public Set<E> findByConditions(String where){ 28 return null; 29 } 30 public static void main(String[] args) { 31 GenericDAO<ReflectField> dao = new GenericDAO<ReflectField>();//這里指定泛型類的操作類型是ReflectField 32 dao.add(new ReflectField(1,3)); 33 ReflectField rf = dao.findById(1); 34 GenericDAO<String> dao1=null; 35 new GenericDAO<String>(); 36 } 37 }
類級別的泛型是根據引用該類名時指定的類型信息來參數化類型變量的,例如,如下的兩種方式都可以:
GenericDAO<String> dao=null;
new GenericDAO<String>();
注意:
1.在對泛型類型進行參數化時,類型參數的實例必須是引用類型,不能是基本類型
2.當一個變量被聲明為泛型時,只能被實例變量和方法調用(還有內嵌類型),而不能被靜態變量和靜態方法調用,因為靜態成員是被所有參數化的類所共享的,所以靜態成員不應該有類級別的類型參數。
九、通過反射獲得泛型的實際類型參數
1 package cn.itcast.day2; 2 import java.lang.reflect.Method; 3 import java.lang.reflect.ParameterizedType; 4 import java.lang.reflect.Type; 5 import java.util.Date; 6 import java.util.Vector; 7 /** 8 * 此類是用來演示如何通過反射獲得泛型的實際類型參數 9 * Hibernate中的源代碼就有這樣的寫法 10 * @author 孤傲蒼狼 11 * 12 */ 13 public class UseReflectGetGenericParameter { 14 public static void main(String[] args) throws Exception { 15 /** 16 * 通過這種方式得到的字節碼中是沒有辦法得到泛型類的實際類型參數的, 17 * 因為在編譯這個泛型類時就已經把這個泛型類的實際參數給去掉了 18 * Vector<Date> v = new Vector<Date>(); 19 * v.getClass(); 20 */ 21 Method applyMethod = UseReflectGetGenericParameter.class.getMethod( 22 "applyVector", Vector.class); 23 //得到泛型類型的參數化類型數組,Type類是Class類的父類 24 Type[] types = applyMethod.getGenericParameterTypes(); 25 /** 26 * ParameterizedType這個類是一個參數化類型類,types數組中存儲的都是參數化類型的參數, 27 * 這里取出第一個數組元素,並強制轉換成ParameterizedType類型 28 */ 29 ParameterizedType pType = (ParameterizedType) types[0]; 30 System.out.println(pType.getRawType()/*得到原始類型,輸出的結果為:class java.util.Vector*/); 31 System.out.println(pType.getActualTypeArguments()[0]/*獲得泛型的實際類型參數,輸出的結果為:class java.util.Date*/); 32 } 33 /** 34 * 利用反射可以得到這個方法的參數列表的類型 35 * 通過這個變量v是沒有辦法知道定義它的那個類型的 36 * 但是當把這個變量交給一個方法作為參數或者返回值去使用, 37 * Method類中提供了一系列方法可以獲得方法的參數列表 38 * 並且是以泛型的那種形式來獲得參數列表 39 * @param v 40 */ 41 public static void applyVector(Vector<Date> v) { 42 } 43 }