本文包括:
- JDK5之前集合對(duì)象使用問(wèn)題
- 泛型的出現(xiàn)
- 泛型應(yīng)用
- 泛型典型應(yīng)用
- 自定義泛型——泛型方法
- 自定義泛型——泛型類(lèi)
- 泛型的高級(jí)應(yīng)用——通配符(wildcard)
- 泛型通配符的擴(kuò)展閱讀
泛型(Generic)
1、JDK5之前集合對(duì)象使用問(wèn)題
可以向集合添加任何類(lèi)型對(duì)象
從集合取出對(duì)象時(shí),數(shù)據(jù)類(lèi)型丟失杭措,使用與類(lèi)型相關(guān)方法搂鲫,強(qiáng)制類(lèi)型轉(zhuǎn)換。
程序存在安全隱患
2同窘、泛型的出現(xiàn)
-
JDK5中的泛型允許程序員使用泛型技術(shù)限制集合的處理類(lèi)型
List<String> list = new ArrayList<String>();
注意:泛型是提供給javac編譯器使用的,它用于限定集合的輸入類(lèi)型,讓編譯器在源代碼級(jí)別上晓殊,即擋住向集合中插入非法數(shù)據(jù)。但編譯器編譯完帶有泛型的java程序后伤提,生成的.class文件中將不再帶有泛型信息巫俺,因此程序運(yùn)行效率不受影響,這個(gè)過(guò)程稱(chēng)為“擦除”肿男。
-
泛型的基本術(shù)語(yǔ)介汹,以ArrayList<E>為例:"<>"讀作typeof
- ArrayList<E>中的E稱(chēng)為類(lèi)型參數(shù)變量
- ArrayList<Integer>中的Integer稱(chēng)為實(shí)際類(lèi)型參數(shù)。
- 整個(gè)ArrayList<Integer>稱(chēng)為參數(shù)化類(lèi)型ParameterizedType
3舶沛、 泛型應(yīng)用
類(lèi)型安全檢查
編寫(xiě)通用Java程序(Java框架)
4嘹承、泛型典型應(yīng)用
-
使用Type-Safe的集合對(duì)象
List
Set
Map
-
List示例:
//使用類(lèi)型安全List List<String> list = new LinkedList<String>(); //因?yàn)槭褂梅盒停荒芴砑覵tring類(lèi)型元素 list.add("aaa"); list.add("bbb"); list.add("ccc"); //遍歷List有三種方法 //方法一:因?yàn)長(zhǎng)ist是有序的(存入順序和取出順序一樣)如庭,通過(guò)size和get方法進(jìn)行遍歷 for (int i = 0; i < list.size(); i++) { String s = list.get(i); System.out.println(s); } //方法二:因?yàn)長(zhǎng)ist繼承Collection接口叹卷,通過(guò)Collection的iterator進(jìn)行遍歷 Iterator<String> iterator = list.iterator(); //遍歷iterator通過(guò)迭代器hasNext和next方法進(jìn)行遍歷 while (iterator.hasNext()) { String s = iterator.next(); System.out.println(s); } //方法三:JDK5引入了foreach循環(huán)結(jié)構(gòu),通過(guò)foreach結(jié)構(gòu)遍歷List for (String s : list) { System.out.println(s); }
-
Set示例:
//使用類(lèi)型安全Set Set<String> set = new TreeSet<String>(); set.add("asd"); set.add("fdf"); set.add("bxc"); //取出Set元素有兩種方法坪它,因?yàn)镾et是無(wú)序的骤竹,所以比List少一種遍歷方法 //方法一:Set繼承Collection,所以可以使用Iterator遍歷 Iterator<String> iterator = set.iterator(); while (iterator.hasNext()) { String s = iterator.next(); System.out.println(s); } //方法二:JDK5引入了foreach for (String s : set) { System.out.println(s); }
-
Map示例:
//使用類(lèi)型安全的Map -- 因?yàn)镸ap是一個(gè)鍵值對(duì)結(jié)構(gòu)往毡,執(zhí)行兩個(gè)類(lèi)型泛型 Map<String, String> map = new HashMap<String, String>(); map.put("aaa", "111"); map.put("bbb", "222"); //取出Map元素有兩種方法 //方法一:通過(guò)Map的keySet()進(jìn)行遍歷 Set<String> keys = map.keySet(); // 獲得key的集合 for (String key : keys) { System.out.println(key + ":" + map.get(key)); } //方法二:通過(guò)map的entrySet()蒙揣,獲得每一個(gè)鍵值對(duì)。 Set<Map.Entry<String, String>> entrySet = map.entrySet(); //每個(gè)元素都是一個(gè)鍵值對(duì) for (Entry<String, String> entry : entrySet) { //通過(guò)entry的getKey()和getValue()獲得每一個(gè)鍵值對(duì)的鍵和值 System.out.println(entry.getKey() + ":" + entry.getValue()); }
5开瞭、自定義泛型——泛型方法
-
Java中的普通方法懒震、構(gòu)造方法和靜態(tài)方法中都可以使用泛型罩息。方法使用泛型前,必須對(duì)泛型進(jìn)行聲明个扰,語(yǔ)法:<T>瓷炮,T可以是任意字母,但通常必須要大寫(xiě)递宅。<T>通常需放在方法的返回值聲明之前娘香。
例如:public static <T> void doxx(T t);
-
假設(shè)有這樣一個(gè)需求,要求實(shí)現(xiàn)指定位置上數(shù)組元素的交換恐锣,這個(gè)數(shù)組中的元素可能是int型茅主,可能是String類(lèi)型。
-
未使用泛型代碼如下:
//String類(lèi)型數(shù)組 public void changePosition(String[] arr, int index1, int index2) { String temp = arr[index1]; arr[index1] = arr[index2]; arr[index2] = temp; } //int類(lèi)型數(shù)組 public void changePosition(int[] arr, int index1, int index2) { int temp = arr[index1]; arr[index1] = arr[index2]; arr[index2] = temp; } Integer[] arr1 = new Integer[] { 1, 2, 3, 4, 5 }; changePosition(arr1, 1, 3); System.out.println(Arrays.toString(arr1)); String[] arr2 = new String[] { "aaa", "bbb", "ccc", "ddd" }; changePosition(arr2, 0, 2); System.out.println(Arrays.toString(arr2));
-
使用泛型代碼如下:
// 使用泛型 編寫(xiě)交換數(shù)組通用方法土榴,類(lèi)型可以String 可以 int --- 通過(guò)類(lèi)型 public <T> void changePosition(T[] arr, int index1, int index2) { T temp = arr[index1]; arr[index1] = arr[index2]; arr[index2] = temp; } Integer[] arr1 = new Integer[] { 1, 2, 3, 4, 5 }; changePosition(arr1, 1, 3); System.out.println(Arrays.toString(arr1)); String[] arr2 = new String[] { "aaa", "bbb", "ccc", "ddd" }; changePosition(arr2, 0, 2); System.out.println(Arrays.toString(arr2));
兩者輸出相同诀姚,所以利用泛型可以編寫(xiě)通用的Java程序
-
6、自定義泛型——泛型類(lèi)
-
如果一個(gè)類(lèi)多處都要用到同一個(gè)泛型玷禽,這時(shí)可以吧泛型定義在類(lèi)上(即類(lèi)級(jí)別的泛型)赫段,語(yǔ)法如下:
public class GenericDao<T>{ private T field1; public void save(T obj){} public T getId(int id){} }
注意:靜態(tài)方法不能使用類(lèi)定義的泛型,應(yīng)該單獨(dú)定義泛型矢赁。
-
示例:
如果在1.5節(jié)中還需要一個(gè)需求:倒序數(shù)組糯笙,那么可以自定義一個(gè)泛型類(lèi):
public class ArraysUtils<A> { // 類(lèi)的泛型 // 將數(shù)組倒序 public void reverse(A[] arr) { /* * 只需要遍歷數(shù)組前一半元素,和后一半元素 對(duì)應(yīng)元素 交換位置 */ for (int i = 0; i < arr.length / 2; i++) { // String first = arr[i]; // String second = arr[arr.length - 1 - i]; A temp = arr[i]; arr[i] = arr[arr.length - 1 - i]; arr[arr.length - 1 - i] = temp; } } public void changePosition(A[] arr, int index1, int index2) { A temp = arr[index1]; arr[index1] = arr[index2]; arr[index2] = temp; } }
對(duì)應(yīng)泛型類(lèi)型參數(shù)起名 T E K V ---- 泛型類(lèi)型可以以任意大寫(xiě)字母命名撩银,建議你使用有意義的字母
如:T Template E Element K key V value
7给涕、泛型的高級(jí)應(yīng)用——通配符(wildcard)
-
假設(shè)有一個(gè)方法,接受一個(gè)集合额获,并打印出集合中的所有元素够庙,如下所示:
// ? 代表任意類(lèi)型 public void print(List<?> list) { // 泛型類(lèi)型 可以是任何類(lèi)型 --- 泛型通配符 for (Object string : list) { System.out.println(string); } } public void demo10() { // 打印數(shù)組中所有元素內(nèi)容 List<String> list = new LinkedList<String>(); list.add("aaa"); list.add("bbb"); list.add("ccc"); print(list); List<Integer> list2 = new LinkedList<Integer>(); list2.add(111); list2.add(222); list2.add(333); print(list2); }
-
只用通配符的情況下很少,通常還需要通過(guò)指定上下邊界抄邀,限制通配符類(lèi)型范圍耘眨。
用法:-
指定上邊界:
List<? extends Number> list = new ArrayList<Integer>(); //繼承自Number境肾,即指定了泛型的上邊界為Number剔难,且包括Number
-
指定下邊界:
List<? super String> list = new ArrayList<Object>(); //是String的父類(lèi),即指定了泛型的下邊界為String奥喻,且包括String
上下邊界不能同時(shí)使用 :
List<? extends Object super Integer> list = new ArrayList<Object>(); //錯(cuò)誤偶宫!沒(méi)有這么寫(xiě)的
-
-
上下邊界的應(yīng)用:
- 范例一:
Set中有方法:addAll(Collection<? extends E> c) //將目標(biāo)集合c的內(nèi)容添加到當(dāng)前set ,? extends E 目標(biāo)集合是E的子類(lèi)型
即有如下代碼衫嵌,可以運(yùn)行成功:
Set<Number> set = new HashSet<Number>(); List<Integer> list = new ArrayList<Integer>(); set.addAll(list); // list 中 Integer 自動(dòng)轉(zhuǎn)換為 Number
- 范例二:
TreeSet有構(gòu)造方法:TreeSet(Comparator<? super E> comparator) //傳入E的父類(lèi)型的比較器
即有如下代碼读宙,可以運(yùn)行成功:
Set<Apple> set = new TreeSet<Apple>(); // 默認(rèn)需要蘋(píng)果比較器排序 class FruitComparator implements Comparator<Fruit> {} //水果的比較器 Set<Apple> set = new TreeSet<Apple>(new FruitComparator()); // 需要Apple比較器 ,傳入 Fruit比較器 楔绞,依據(jù)構(gòu)造方法结闸,可行
-
錯(cuò)誤范例:
public void add(List<? extends Number> list){ list.add(100); //會(huì)報(bào)錯(cuò)!使用通配符后酒朵,不要使用與類(lèi)型相關(guān)的方法桦锄。 }
8、泛型通配符的擴(kuò)展閱讀
關(guān)于泛型還可深入研究蔫耽,在《Effective Java 2th Edition》有相關(guān)介紹结耀,感興趣的同學(xué)可以閱讀一下。
-
最后還介紹一下關(guān)于泛型通配符的上下邊界問(wèn)題匙铡,什么時(shí)候用上邊界图甜,什么時(shí)候用下邊界?
PECS:producer extends consumer super頻繁往外讀取內(nèi)容的鳖眼,適合用上界Extends黑毅。
經(jīng)常往里插入的,適合用下界Super钦讳。
-
例如:
// compile error // List <? extends Fruit> appList2 = new ArrayList(); // appList2.add(new Fruit()); // appList2.add(new Apple()); // appList2.add(new RedApple()); // no error List <? super Fruit> appList = new ArrayList(); appList.add(new Fruit()); appList.add(new Apple()); appList.add(new RedApple());
http://stackoverflow.com/questions/2723397/what-is-pecs-producer-extends-consumer-super