咱們最大的弱點在於放棄。成功的必然之路就是不斷的重來一次。 --達爾文java
建議93:Java的泛型是能夠擦除的數組
建議94:不能初始化泛型參數和數組安全
建議95:強制聲明泛型的實際類型函數
建議96:不一樣的場景使用不一樣的泛型通配符工具
建議97:警戒泛型是不能協變和逆變的性能
建議98:list中泛型順序爲T、?、Objectui
建議99:嚴格限定泛型類型採用多重界限編碼
建議100:數組的真實類型必須是泛型類型的子類型spa
泛型能夠減小將至類型轉換,能夠規範集合的元素類型,還能夠提升代碼的安全性和可讀性,優先使用泛型。.net
反射能夠「看透」程序的運行狀況,可讓咱們在運行期知曉一個類或實例的運行狀況,能夠動態的加載和調用,雖然有必定的性能憂患,但它帶給咱們的便利大於其性能缺陷。
建議93:Java的泛型是能夠擦除的
一、Java泛型的引入增強了參數類型的安全性,減小了類型的轉換,Java的泛型在編譯器有效,在運行期被刪除,也就是說全部的泛型參數類型在編譯後會被清除掉,咱們來看一個例子,代碼以下:
兩個同樣的方法衝突了?
這就是Java泛型擦除引發的問題:在編譯後全部的泛型類型都會作相應的轉化。轉換規則以下:
- List<String>、List<Integer>、List<T>擦除後的類型爲List
- List<String>[] 擦除後的類型爲List[].
- List<? extends E> 、List<? super E> 擦除後的類型爲List<E>.
- List<T extends Serializable & Cloneable >擦除後的類型爲List< Serializable>.
二、明白了這些規則,再看以下代碼:
public static void main(String[] args) { List<String> list = new ArrayList<String>(); list.add("abc"); String str = list.get(0); }
進過編譯後的擦除處理,上面的代碼和下面的程序時一致的:
public static void main(String[] args) { List list = new ArrayList(); list.add("abc"); String str = (String) list.get(0); }
三、Java之因此如此處理,有兩個緣由:
① 避免JVM的運行負擔。
若是JVM把泛型類型延續到運行期,那麼JVM就須要進行大量的重構工做了。
② 版本兼容
在編譯期擦除能夠更好的支持原生類型(Raw Type),在Java1.5或1.6...平臺上,即便聲明一個List這樣的原生類型也是能夠正常編譯經過的,只是會產生警告信息而已。
四、明白了Java泛型是類型擦除的,咱們就能夠解釋相似以下的問題了:
① 泛型的class對象是相同的:每一個類都有一個class屬性,泛型化不會改變class屬性的返回值,例如:
public static void main(String[] args) { List<String> list = new ArrayList<String>(); List<Integer> list2 = new ArrayList<Integer>(); System.out.println(list.getClass()); System.out.println(list.getClass()==list2.getClass()); }
以上代碼返回true,緣由很簡單,List<String>和List<Integer>擦除後的類型都是List,沒有任何區別。
② 泛型數組初始化時不能聲明泛型,以下代碼編譯時通不過:
List<String>[] listArray = new List<String>[];
緣由很簡單,能夠聲明一個帶有泛型參數的數組,但不能初始化該數組,由於執行了類型擦除操做,List<Object>[]與List<String>[] 就是同一回事了,編譯器拒絕如此聲明。
③ instanceof不容許存在泛型參數
如下代碼不能經過編譯,緣由同樣,泛型類型被擦除了:
建議94:不能初始化泛型參數和數組
泛型類型在編譯期被擦除,咱們在類初始化時將沒法得到泛型的具體參數,好比這樣的代碼:
這段代碼是編譯不過的,由於編譯時須要得到T類型,但泛型在編譯期類型已經被擦除了。在某些狀況下,咱們須要泛型數組,那該如何處理呢?代碼以下:
public class Student<T> { // 再也不初始化,由構造函數初始化 private T t; private T[] tArray; private List<T> list = new ArrayList<T>(); // 構造函數初始化 public Student() { try { Class<?> tType = Class.forName(""); t = (T) tType.newInstance(); tArray = (T[]) Array.newInstance(tType, 5); } catch (Exception e) { e.printStackTrace(); } } }
此時,運行就沒有什麼問題了,剩下的問題就是怎麼在運行期得到T的類型,也就是tType參數,通常狀況下泛型類型是沒法獲取的,不過,在客戶端調用時多傳輸一個T類型的class就會解決問題。
類的成員變量是在類初始化前初始化的,因此要求在初始化前它必須具備明確的類型,不然就只能聲明,不能初始化。
建議95:強制聲明泛型的實際類型
Arrays工具類有一個方法asList能夠把一個邊長參數或數組轉變爲列表,但它有一個缺點:它所生成的list長度是不可變的,而在咱們的項目開發中有時會很不方便。若是指望可變,那就須要寫一個數組的工具類了,代碼以下:
class ArrayUtils { // 把一個變長參數轉化爲列表,而且長度可變 public static <T> List<T> asList(T... t) { List<T> list = new ArrayList<T>(); Collections.addAll(list, t); return list; } }
這很簡單,與Arrays.asList的調用方式相同,咱們傳入一個泛型對象,而後返回相應的List,代碼以下:
public static void main(String[] args) { // 正經常使用法 List<String> list1 = ArrayUtils.asList("A", "B"); // 參數爲空 List list2 = ArrayUtils.asList(); // 參數爲整型和浮點型的混合 List list3 = ArrayUtils.asList(1, 2, 3.1); }
這裏有三個變量須要說明:
一、變量list1:變量list1是一個常規用法,沒有任何問題,泛型實際參數類型是String,返回結果就是一個容納String元素的List對象。
二、變量list2:變量list2它容納的是什麼元素呢?咱們沒法從代碼中推斷出list2列表到底容納的是什麼元素(由於它傳遞的參數是空,編譯器也不知道泛型的實際參數類型是什麼),不過,編譯器會很聰明地推斷出最頂層類Object就是其泛型類型,也就是說list2的完整定義以下:
List<Object> list2 = ArrayUtils.asList();
如此一來,編譯器就不會給出" unchecked "警告了。如今新的問題又出現了:若是指望list2是一個Integer類型的列表,而不是Object列表,由於後續的邏輯會把Integer類型加入到list2中,那該如何處理呢?
強制類型轉換(把asList強制轉換成List<Integer>)?行不通,雖然Java泛型是編譯期擦出的,可是List<Object>和List<Integer>沒有繼承關係,不能強制轉換。
從新聲明一個List<Integer>,而後讀取List<Object>元素,一個一個地向下轉型過去?麻煩,並且效率又低。
最好的解決辦法是強制聲明泛型類型,代碼以下:
List<Integer> intList = ArrayUtils.<Integer>asList();
就這麼簡單,asList方法要求的是一個泛型參數,那咱們就在輸入前定義這是一個Integer類型的參數,固然,輸出也是Integer類型的集合了。
三、變量list3:變量list3有兩種類型的元素:整數類型和浮點類型,那它生成的List泛型化參數應該是什麼呢?是Integer和Float的父類Number?你過高看編譯器了,它不會如此推斷的,當它發現多個元素的實際類型不一致時就會直接確認泛型類型是Object,而不會去追索元素的公共父類是什麼,可是對於list3,咱們更指望它的泛型參數是Number,都是數字嘛,參照list2變量,代碼修改以下:
List<Number> list3 = ArrayUtils.<Number>asList(1, 2, 3.1);
Number是Integer和Float的父類,先把三個輸入參數、輸出參數同類型,問題是咱們要在何時明確泛型類型呢?一句話:沒法從代碼中推斷出泛型的狀況下,便可強制聲明泛型類型。
建議96:不一樣的場景使用不一樣的泛型通配符
Java泛型支持通配符(Wildcard),能夠單獨使用一個「?」表示任意類,也可使用extends關鍵字表示某一個類(接口)的子類型,還可使用super關鍵字表示某一個類(接口)的父類型,但問題是何時該用extends,什麼該用super呢?
一、泛型結構只參與 「讀」 操做則限定上界(extends關鍵字),也就是要界定泛型的上界
編譯失敗,失敗的緣由是list中的元素類型不肯定,也就是編譯器沒法推斷出泛型類型究竟是什麼,是Integer類型?是Double?仍是Byte?這些都符合extends關鍵字的定義,因爲沒法肯定實際的泛型類型,因此編譯器拒絕了此類操做。
二、泛型結構只參與「寫」 操做則限定下界(使用super關鍵字),也就是要界定泛型的下界
甭管它是Integer的123,仍是浮點數3.14,均可以加入到list列表中,由於它們都是Number的類型,這就保證了泛型類的可靠性。
建議97:警戒泛型是不能協變和逆變的
協變:窄類型替換寬類型
逆變:寬類型替換窄類型
一、泛型不支持協變,編譯不經過,,,窄類型變成寬類型(Integer>>Number)
泛型不支持協變,但可使用通配符模擬協變,代碼以下:
" ? extends Number " 表示的意思是,容許Number的全部子類(包括自身) 做爲泛型參數類型,但在運行期只能是一個具體類型,或者是Integer類型,或者是Double類型,或者是Number類型,也就是說通配符只在編碼期有效,運行期則必須是一個肯定的類型。
二、泛型不支持逆變
" ? super Integer " 的意思是能夠把全部的Integer父類型(自身、父類或接口) 做爲泛型參數,這裏看着就像是把一個Number類型的ArrayList賦值給了Integer類型的List,其外觀相似於使用一個寬類型覆蓋一個窄類型,它模擬了逆變的實現。
建議98:list中泛型順序爲T、?、Object
List<T>、List<?>、List<Object>這三者均可以容納全部的對象,但使用的順序應該是首選List<T>,次之List<?>,最後選擇List<Object>,緣由以下:
一、List<T>是肯定的某一類型
List<T>表示的是list集合中的元素是T類型,具體類型在運行期決定;
List<?>表示的是任意類型,與List<T>類型,而List<Object>表示list集合中的全部元素爲Object類型,從字面意義上來分析,List<T>更符合習慣,編譯者知道它是某一個類型,只是在運行期肯定而已。
二、List<T>能夠進行讀寫操做,不能進行增長修改操做,由於編譯器不知道list中容納的是什麼類型的元素,也就沒法校驗類型是否安全。
而List<?>讀取出的元素都是Object類型的,須要主動轉型,因此它常常用於泛型方法的返回值。注意List<?>雖然沒法增長,修改元素,可是卻能夠刪除元素,好比執行remove、clear等方法,那是由於它的刪除動做與泛型類型無關。
List<Object> 也能夠讀寫操做,可是它執行寫入操做時須要向上轉型(Up cast),在讀取數據的時候須要向下轉型,而此時已經失去了泛型存在的意義了。
建議99:嚴格限定泛型類型採用多重界限
在Java的泛型中,可使用&符號關聯多個上界(extends)並實現多個邊界限定,下界(super)沒有多重限定的狀況。
建議100:數組的真實類型必須是泛型類型的子類型
List接口的toArray方法能夠把一個集合轉化爲數組,可是使用不方便,toArray()方法返回的是一個Object數組,因此須要自行轉變。toArray(T[] a)雖然返回的是T類型的數組,可是還須要傳入一個T類型的數組,這也挺麻煩的,咱們指望輸入的是一個泛型化的List,這樣就能轉化爲泛型數組了,來看看能不能實現,代碼以下:
package OSChina.Genericity; import java.util.Arrays; import java.util.List; public class GenericFruit { public static <T> T[] toArray(List<T> list) { T[] t = (T[]) new Object[list.size()]; for (int i = 0, n = list.size(); i < n; i++) { t[i] = list.get(i); } return t; } public static void main(String[] args) { List<String> list = Arrays.asList("A","B"); for(String str :toArray(list)){ System.out.println(str); } } }
編譯沒有任何問題,運行後出現以下異常:
數組是一個容器,只有確保容器內的全部元素類型與指望的類型有父子關係時才能轉換,Object數組只能保證數組內的元素時Object類型,卻不能確保它們都是String的父類型或子類,因此類型轉換失敗。
總而言之,就是數組使用具體類型使用就完了。