java泛型
通俗的講,泛型就是操作類型的占位符
一、通常泛型的寫法
1.1定義泛型類
public class ApiResult<T>{ int resultCode; String resultMsg; T resultObject; } |
1.2定義泛型方法
public JsonUtil{ public <T> T str2Json(String jsonText, Class target){ T result = null; ...
return result; } } |
1.3 K,V類型
public class ResultMap<K,V>{ private K key; private V value;
// 省略set,get方法
public void put(K key, V value){ this.key = key; this.value = value; } } |
二、使用泛型類和方法
ApiResult<User> result = new ApiResult<User>(); result.setResultCode(0); result.setResultMsg("success"); result.setResultObject(new User());
String userJsonText = "userJsonText"; User u = JsonUtil.str2Json(jsonText, User.class); |
ResultMap<String,User> resultMap = new ResultMap<String,User>(); resultMap.put("currentUserKey", new User()); |
三、類型擦除
public class Operate{ public static void main(String[] args){ List<String> names = new ArrayList<String>(); names.add("Jack"); names.add("Tom"); for(String name:names){ System.out.println(name); } } } |
其對應的class文件反匯編以后,我們使用java-gui反編譯.exe 查看編譯之后的代碼如下:
發現沒有,根本沒有<String> 這一部分了。這個限制為String類型的泛型被“擦除”了。寫代碼的時候,泛型會做校驗,類型不對應的,無法add,但是編譯之后邊去掉了泛型類型。
四、為什么要使用java泛型??
這里的泛型就相當於“約法三章”,先給你定好“規矩”,我這個List<String> 就是用來操作String類型的,你插入Person對象就不行。說白了就是為了類型安全。所以其好處有:
4.1類型安全:
通過知道使用泛型定義的變量的類型限制,編譯器可以在一個高得多的程度上驗證類型假設。沒有泛型,這些假設就只存在於程序員的頭腦中(或者如果幸運的話,還存在於代碼注釋中)。
4.2消除強制類型轉換
//該代碼不使用泛型: List li = new ArrayList(); li.put(new Integer(3)); Integer i = (Integer) li.get(0);
//該代碼使用泛型: List<Integer> li = new ArrayList<Integer>(); li.put(new Integer(3)); Integer i = li.get(0); |
public class Fruit {
} |
public class Apple extends Fruit {
} |
public class Plate<T> { private T item; public Plate(T t){ item = t; } public T getItem() { return item; } public void setItem(T item) { this.item = item; }
} |
public class TestGenericParadigm {
public static void main(String[] args){
Plate<Fruit> p = new Plate<Apple>(new Apple()); // 編譯不通過 Plate<Fruit> p = new Plate<Fruit>(new Fruit()); } } |
為什么上面這個會出現編譯不通過??
解釋:蘋果是水果,但是裝蘋果的盤子不是裝水果的盤子
所以,就算容器里裝的東西之間有繼承關系,但容器之間是沒有繼承關系的
為了讓泛型用起來更舒服,Sun的大腦袋們就想出了<? extends T>和<? super T>的辦法,來讓”水果盤子“和”蘋果盤子“之間發生關系
什么是上界?
Plate<? extends Fruit> |
翻譯成人話就是:一個能放水果以及一切是水果派生類的盤子。再直白點就是:啥水果都能放的盤子。這和我們人類的邏輯就比較接近了。Plate<? extends Fruit>和Plate<Apple>最大的區別就是:Plate<? extends Fruit>是Plate<Fruit>以及Plate<Apple>的基類。直接的好處就是,我們可以用“蘋果盤子”給“水果盤子”賦值了。
Plate<? extends Fruit> p=new Plate<Apple>(new Apple());
如果把Fruit和Apple的例子再擴展一下
//Lev 1 class Food{}
//Lev 2 class Fruit extends Food{} class Meat extends Food{}
//Lev 3 class Apple extends Fruit{} class Banana extends Fruit{} class Pork extends Meat{} class Beef extends Meat{}
//Lev 4 class RedApple extends Apple{} class GreenApple extends Apple{} |
在這個體系中,上界通配符 Plate<? extends Fruit> 覆蓋下圖中藍色的區域
什么是下界?
Plate<? super Fruit> |
表達的就是相反的概念:一個能放水果以及一切是水果基類的盤子。Plate<? super Fruit>是Plate<Fruit>的基類,但不是Plate<Apple>的基類。對應剛才那個例子,Plate<? super Fruit>覆蓋下圖中紅色的區域。
上下界通配符的副作用
邊界讓Java不同泛型之間的轉換更容易了。但不要忘記,這樣的轉換也有一定的副作用。那就是容器的部分功能可能失效。
還是以剛才的Plate為例。我們可以對盤子做兩件事,往盤子里set()新東西,以及從盤子里get()東西。
public class TestGenericParadigm {
public static void main(String[] args){ /* * 上界通配符 * 不能存入任何元素 * 讀出來的東西只能放在Fruit或它的基類中 */
Plate<? extends Fruit> p = new Plate<Apple>(new Apple());
p.setItem(new Fruit()); //Error p.setItem(new Apple()); //Error
Fruit newFruit1 = p.getItem(); Object newFruit2 = p.getItem(); Apple new Fruit3 = p.getItem();//Error
} } |
原因是編譯器只知道容器內是Fruit或者它的派生類,但具體是什么類型不知道
所以通配符<?>和類型參數的區別就在於,對編譯器來說所有的T都代表同一種類型。比如下面這個泛型方法里,三個T都指代同一個類型,要么都是String,要么都是Integer。
但通配符<?>沒有這種約束,Plate<?>單純的就表示:盤子里放了一個東西,是什么我不知道
下界<? super T>不影響往里存,但往外取只能放在Object對象里
使用下界<? super Fruit>會使從盤子里取東西的get( )方法部分失效,只能存放到Object對象里。set( )方法正常。
/* * 下界通配符 * 存入元素正常 * 讀取的東西只能存放在Object中 */ Plate<? super Fruit> p1 = new Plate<Fruit>(new Fruit());
p1.setItem(new Fruit()); p1.setItem(new Apple());
Apple newFruit4 = p1.getItem();//Error Fruit newFruit5 = p1.getItem();//Error Object newFruit6 = p1.getItem(); |
因為下界規定了元素的最小粒度的下限,實際上是放松了容器元素的類型控制。既然元素是Fruit的基類,那往里存粒度比Fruit小的都可以。但往外讀取元素就費勁了,只有所有類的基類Object對象才能裝下。但這樣的話,元素的類型信息就全部丟失
PECS原則
最后看一下什么是PECS(Producer Extends Consumer Super)原則,已經很好理解了:
頻繁往外讀取內容的,適合用上界Extends。
經常往里插入的,適合用下界Super。