泛型提供了一種將集合類型傳達給編譯器的方法,一旦編譯器知道了集合元素的類型,編譯器就能夠對其類型進行檢查,作類型約束。java
在沒有泛型以前:spring
/** * 迭代 Collection ,注意 Collection 裏面只能是 String 類型 */ public static void forEachStringCollection(Collection collection) { Iterator iterator = collection.iterator(); while (iterator.hasNext()) { String next = (String) iterator.next(); System.out.println("next string : " + next); } }
這是使用泛型以後的程序:編程
public static void forEachCollection(Collection<String> collection) { Iterator<String> iterator = collection.iterator(); while (iterator.hasNext()) { String next = iterator.next(); System.out.println("next string : " + next); } }
在沒有泛型以前,咱們只能經過更直觀的方法命名和 doc 註釋來告知方法的調用者,forEachStringCollection
方法只能接收元素類型爲String
的集合。然而這只是一種「約定」,若是使用方傳入了一個元素不爲String
類型的集合,在編譯期間代碼並不會報錯,只有在運行時,會拋出ClassCastException
異常,這對調用方來講並不友好。json
經過泛型,能夠將方法的 doc 註釋轉移到了方法簽名上:forEachCollection(Collection<String> collection)
,方法調用者一看方法簽名便知道此處須要一個Collection<String>
,編譯器也能夠在編譯時檢查是否違反類型約束。須要說明的是,編譯器的檢查也是很是容易繞過的,如何繞過呢?請看下文哦~安全
畫外音:代碼就是最好的註釋。微信
思考,如下代碼是否合法:app
List<String> strList = new ArrayList<>(); List<Object> objList = new ArrayList<>(); objList.add("公衆號:Coder小黑"); // 代碼1 objList = strList; // 代碼2
廢話很少說,直接上答案。框架
代碼1
很明顯是合法的。Object
類型是String
類型的父類。spa
那麼代碼2
爲何不合法呢?debug
在 Java 中,對象類型的賦值實際上是引用地址的賦值,也就是說,假設代碼2
賦值成功,objList
和strList
變量引用的是同一個地址。那會有什麼問題呢?
若是此時,往objList
中添加了一個非String
類型的元素,也就至關於往strList
中添加了一個非String
類型的元素。很明顯,此處就破壞了List<String> strList
。因此,Java 編譯器會認爲代碼2
是非法的,這是一種安全的作法。
畫外音:可能和大多數人的直覺不太同樣,那是咱們考慮問題還不夠全面,此處的緣由比結果更重要哦
咱們已經知道,上文的代碼2
是不合法的。那麼,接下來思考這樣兩個方法:
public static void printCollection1(Collection c) {} public static void printCollection2(Collection<Object> c) {}
這兩個方法有什麼區別呢?
printCollection1
方法支持任意元素類型的Collection
,而printCollection2
方法只能接收Object
類型的Collection
。雖然String
是Object
的子類,可是Collection<String>
並非Collection<Object>
的子類,和代碼2
有殊途同歸之妙。
再看一下下面這個方法:
public static void printCollection3(Collection<?> c) {}
printCollection3
和上面的兩個方法又有什麼區別呢?怎麼理解printCollection3
方法上的?
呢?
?
表示任意類型,代表printCollection3
方法接收任意類型的集合。
好,那麼問題又來了,請看以下代碼:
List<?> c = Lists.newArrayList(new Object()); Object o = c.get(0); c.add("12"); // 編譯錯誤
爲何會編譯報錯呢?
咱們能夠將任意類型的集合賦值給List<?> c
變量。可是,add
方法的參數類型是?
,它表示未知類型,因此調用add
方法時會編程錯誤,這是一種安全的作法。
而get
方法返回集合中的元素,雖然集合中的元素類型未知,可是不管是什麼類型,其均爲Object
類型,因此使用Object
類型來接收是安全的。
public static class Person extends Object {} public static class Teacher extends Person {} // 只知道這個泛型的類型是Person的子類,具體是哪個不知道 public static void method1(List<? extends Person> c) {} // 只知道這個泛型的類型是Teacher的父類,具體是哪個不知道 public static void method2(List<? super Teacher> c) {}
思考以下代碼運行結果:
public static void test3() { List<Teacher> teachers = Lists.newArrayList(new Teacher(), new Teacher()); // method1 處理的是 Person 的 子類,Teacher 是 Person 的子類 method1(teachers); } // 只知道這個泛型的類型是Person的子類,具體是哪個不知道 public static void method1(List<? extends Person> c) { // Person 的子類,轉Person, 安全 Person person = c.get(0); c.add(new Person()); //代碼3,編譯錯誤 }
代碼3
爲何會編譯錯誤呢?
method1
只知道這個泛型的類型是Person
的子類,具體是哪個不知道。若是代碼3
編譯成功,那麼上述的代碼中,就是往List<Teacher> teachers
中添加了一個Person
元素。此時,後續在操做List<Teacher> teachers
時,大機率會拋出ClassCastException
異常。
再來看以下代碼:
public static void test4() { List<Person> teachers = Lists.newArrayList(new Teacher(), new Person()); // method1 處理的是 Person 的 子類,Teacher 是 Person 的子類 method2(teachers); } // 只知道這個泛型的類型是Teacher的父類,具體是哪個不知道 public static void method2(List<? super Teacher> c) { // 具體是哪個不知道, 只能用Object接收 Object object = c.get(0); // 代碼4 c.add(new Teacher()); // 代碼5,不報錯 }
method2
泛型類型是Teacher
的父類,而Teacher
的父類有不少,因此代碼4
只能使用Object
來接收。子類繼承父類,因此往集合中添加一個Teacher
對象是安全的操做。
PECS:producer extends, consumer super
。
<? extends T>
<? super T>
怎麼理解呢?咱們直接上代碼:
/** * producer - extends, consumer- super */ public static void addAll(Collection<? extends Object> producer, Collection<? super Object> consumer) { consumer.addAll(producer); }
有同窗可能會說,這個原則記不住怎麼辦?
不要緊,筆者有時候也記不清。不過幸運的是,在 JDK 中有這個一個方法:java.util.Collections#copy
,該方法很好的闡述了 PECS 原則。每次想用又記不清的時候,看一眼該方法就明白了~
// java.util.Collections#copy public static <T> void copy(List<? super T> dest, List<? extends T> src){}
畫外音:知識不少、很雜,咱們應該在大腦中創建索引,遇到問題,經過索引來快速查找解決方法
上述的一些檢查都是編譯時的檢查,而想要騙過編譯器的檢查也很簡單:
public static void test5() { List<Integer> list = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5)); List copy = list; copy.add("a"); List<Integer> list2 = copy; }
test5
方法就騙過了編譯器,並且能成功運行。
那何時會報錯呢?當程序去讀取list2
中的元素時,纔會拋出ClassCastException
異常。
Java 給咱們提供了java.util.Collections#checkedList
方法,在調用add
時就會檢查類型是否匹配。
public static void test6() { List<Integer> list = Collections.checkedList(Arrays.asList(1, 2, 3, 4, 5), Integer.class); List copy = list; // Exception in thread "main" java.lang.ClassCastException: Attempt to insert class java.lang.String element into collection with element type class java.lang.Integer copy.add("a"); }
畫外音:這是一種 fail-fast 的思想,在 add 時發現類型不一致馬上報錯,而不是繼續運行可能存在問題的程序
咱們知道,編譯器會將泛型擦除,那怎麼理解泛型擦除呢?是統一改爲Object
嗎?
泛型擦除遵循如下規則:
Object
。public class TypeErasureDemo { public <T> void forEach(Collection<T> collection) {} public <E extends String> void iter(Collection<E> collection) {} }
使用javap
命令查看 Class 文件信息:
經過 Class 文件信息能夠看到:編譯器將forEach
方法的泛型替換爲了Object
,將iter
方法的泛型替換爲了String
。
瞭解完泛型擦除規則以後,咱們來看一下當泛型遇到方法重載,會遇到什麼樣的問題呢?
閱讀以下代碼:
// 第一組 public static void printArray(Object[] objs) {} public static <T> void printArray(T[] objs) {}
// 第二組 public static void printArray(Object[] objs) {} public static <T extends Person> void printArray(T[] objs) {}
上面兩組方法是否都構成了重載呢?
T[]
其實就是Object[]
,所以第一組不構成重載。<T extends Person>
代表接收的方法是Person
的子類,構成重載。Spring 框架中提供了org.springframework.core.ResolvableType
來優雅解析泛型。
一個簡單的使用示例以下:
public class ResolveTypeDemo { private static final List<String> strList = Lists.newArrayList("a"); public <T extends CharSequence> void exchange(T obj) {} public static void resolveFieldType() throws Exception { Field field = ReflectionUtils.findField(ResolveTypeDemo.class, "strList"); ResolvableType resolvableType = ResolvableType.forField(field); // class java.lang.String System.out.println(resolvableType.getGeneric(0).resolve()); } public static void resolveMethodParameterType() throws Exception { Parameter[] parameters = ReflectionUtils.findMethod(ResolveTypeDemo.class, "exchange", CharSequence.class).getParameters(); ResolvableType resolvableType = ResolvableType.forMethodParameter(MethodParameter.forParameter(parameters[0])); // interface java.lang.CharSequence System.out.println(resolvableType.resolve()); } public static void resolveInstanceType() throws Exception { PayloadApplicationEvent<String> instance = new PayloadApplicationEvent<>(new Object(), "hi"); ResolvableType resolvableTypeForInstance = ResolvableType.forInstance(instance); // class java.lang.String System.out.println(resolvableTypeForInstance.as(PayloadApplicationEvent.class).getGeneric().resolve()); } }
最近看到這樣一個代碼,使用 Jackson 將 JSON 轉化爲 Map。
public class JsonToMapDemo { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); public static <K, V> Map<K, V> toMap(String json) throws JsonProcessingException { return (Map) OBJECT_MAPPER.readValue(json, new TypeReference<Map<K, V>>() { }); } public static void main(String[] args) throws JsonProcessingException { // {"1":{"id":1}} String json = "{\"1\":{\"id\":1}}"; Map<Integer, User> userIdMap = OBJECT_MAPPER.readValue(json, new TypeReference<Map<Integer, User>>() { }); userIdMap.forEach((integer, user) -> { System.out.println(user.getId()); }); } @Data public static class User implements Serializable { private static final long serialVersionUID = 8817514749356118922L; private int id; } }
運行 main 方法,代碼雖然正常結束。可是這個代碼實際上是有問題的,有什麼問題呢?一塊兒來看以下代碼:
public static void main(String[] args) { // {"1":{"id":1}} String json = "{\"1\":{\"id\":1}}"; Map<Integer, User> userIdMap = toMap(json); userIdMap.forEach((integer, user) -> { // 出處代碼會報錯 // Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer System.out.println(user.getId()); }); }
爲何會報ClassCastException
呢?讓咱們來 Debug 一探究竟。
經過 Debug 能夠發現:Map<Integer, User> userIdMap
對象的 key 實際上是String
類型,而 value 是一個LinkedHashMap
。這很好理解,上述代碼這個寫法,根本不知道 K,V 是什麼。正確寫法以下:
public static void main(String[] args) throws JsonProcessingException { // {"1":{"id":1}} String json = "{\"1\":{\"id\":1}}"; Map<Integer, User> userIdMap = OBJECT_MAPPER.readValue(json, new TypeReference<Map<Integer, User>>() { }); userIdMap.forEach((integer, user) -> { System.out.println(user.getId()); }); }
歡迎關注微信公衆號:Coder小黑