昨天,在逛論壇時遇到個這麼個問題,上代碼:java
public class GenericTest { //方法一 public static <T extends Comparable<T>> List<T> sort(List<T> list) { return Arrays.asList(list.toArray((T[]) new Comparable[list.size()])); } //方法二 public static <T extends Comparable<T>> T[] sort2(List<T> list) { // 這裏沒報錯 return list.toArray((T[]) new Comparable[list.size()]); } public static void main(String[] args) { List<Integer> list = new ArrayList<>(); list.add(1); list.add(2); // 方法一調用正常 System.out.println(sort(list).getClass()); // 方法二調用報錯了,這裏報錯了 System.out.println(sort2(list).getClass()); } }
這個問題有如下四個現象:數組
(1)方法一調用徹底正常;code
(2)方法二調用報錯了;get
(3)方法二報錯的地方是在System.out.println(sort2(list).getClass());
這行,而不是return list.toArray((T[]) new Comparable[list.size()]);
這行;源碼
(4)報的錯是[Ljava.lang.Comparable; cannot be cast to [Ljava.lang.Integer;
;io
怎麼樣?你心中有答案嘛?類型擦除?怎麼擦?摩擦摩擦?編譯
剛拿到這道題,我也是一臉懵逼,這要報錯也應該是在return list.toArray((T[]) new Comparable[list.size()]);
這行啊,並且要報錯應該兩個方法都報錯啊。ast
抱着不放棄不拋棄的心態,彤哥作了大量的實驗,終於得出了泛型的本質,且聽我娓娓道來。class
首先,咱們要明白,java中的數組是不支持向下轉型的,可是若是自己就是那個類型的是能夠轉過去的,請看下面的例子:泛型
public static void main(String[] args) { Object[] objs = new Object[]{1}; // 類型轉換錯誤 // Integer[] ins = (Integer[]) objs; Object[] objs2 = new Integer[]{1}; // 不報錯 Integer[] ins2 = (Integer[]) objs2; }
java裏的泛型是假泛型,只在編譯期有效,在運行時是沒有泛型的概念的,舉個簡單的例子:
public static void main(String[] args) { List<String> strList = Arrays.asList("1"); List<Integer> intList = Arrays.asList(1); // 打印:true System.out.println(strList.getClass() == intList.getClass()); }
能夠看到兩個list的類型是同樣的,若是你以爲這個例子不夠說服力,那我給你個過度點的例子:
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { List<String> strList = new ArrayList<>(); Method addMethod = strList.getClass().getMethod("add", Object.class); addMethod.invoke(strList, 1); addMethod.invoke(strList, true); addMethod.invoke(strList, new Long(1)); addMethod.invoke(strList, new Byte[]{1}); // 打印:[1, true, 1, 1] System.out.println(strList); }
瞧,我能夠往一個String類型的List中扔任何我想扔的東西,服不服?!
因此說java裏面的泛型是假的,運行時不存在滴。
數組不能向下強轉我懂了,類型擦除我也懂了,彷佛仍是過很差這一輩子,呃不是,是仍是解決不了這道題啊?
呃,好像是~~
咱們再來看一個簡單的例子:
// GenericTest2.java(源碼) public class GenericTest2 { public static void main(String[] args) { System.out.println(raw("1")); } public static <T> T raw(T t) { return t; } } // GenericTest2.class(反編譯) public class GenericTest2 { public GenericTest2() { } public static void main(String[] args) { System.out.println((String)raw("1")); } public static <T> T raw(T t) { return t; } }
嗯~彷佛看出來點端倪,反編譯後多了個構造方法。
呃,沒錯。還有呢?
仔細一看,System.out.println((String)raw("1"));
這一句多加了個String強轉。
這就是關鍵所在,結合類型擦除,運行時並無所謂的泛型,因此raw()返回的實際上是Object,可是調用者本身知道我要的是String類型啊,因此我就知道強轉一下嘍。
咱們再來看個極端的例子:
// GenericTest2.java(源碼) public class GenericTest2 { public static void main(String[] args) { System.out.println(raw("1")); } public static <T> T raw(T t) { return (T)new Integer(1); } } // GenericTest2.class(反編譯) public class GenericTest2 { public GenericTest2() { } public static void main(String[] args) { System.out.println((String)raw("1")); } public static <T> T raw(T t) { return new Integer(1); } }
仔細觀察,能夠發現,raw()方法裏的強轉(T)new Integer(1)
變成了new Integer(1)
,強轉被擦除了,實際上在運行時這裏的T變成了Object,全部類型都是Object的子類,也就不須要強轉了。
而(String)raw("1")
的強轉仍是加上的,這是調用者知道類型是String,因此raw()返回後本身強轉成String一下。
固然,這個代碼運行是會報錯的,java.lang.Integer cannot be cast to java.lang.String
,由於raw()返回的是Integer類型,強轉成String類型失敗了。
好了,基本思路就是這樣。
咱們上面舉的例子都是泛型方法,那麼泛型類呢?
一樣地,咱們來看個例子:
// GenericTest3.java(源碼) public class GenericTest3 { public static void main(String[] args) { System.out.println(new Raw<String>().raw("1")); } } class Raw<T> { public T raw(T t) { return (T)new Integer(1); } } // GenericTest3.class(反編譯) public class GenericTest3 { public GenericTest3() { } public static void main(String[] args) { System.out.println((String)(new Raw()).raw("1")); } } class Raw<T> { Raw() { } public T raw(T t) { return new Integer(1); } }
能夠看到,跟泛型方法的表現如出一轍。固然,這裏運行時也會報java.lang.Integer cannot be cast to java.lang.String
這個錯誤。
java中的泛型只在編譯期有效,在運行時只有調用者知道須要什麼類型,且調用者調用泛型方法後本身作強制轉換,被調用者是徹底無感的。
因此,出現問題不要問被調用者,而是要問調用者,你丫是怎麼調用的?!
爲了方便咱們仍是把開篇的問題拿過來。
// GenericTest.java(源碼) public class GenericTest { //方法一 public static <T extends Comparable<T>> List<T> sort(List<T> list) { return Arrays.asList(list.toArray((T[]) new Comparable[list.size()])); } //方法二 public static <T extends Comparable<T>> T[] sort2(List<T> list) { // 這裏沒報錯 return list.toArray((T[]) new Comparable[list.size()]); } public static void main(String[] args) { List<Integer> list = new ArrayList<>(); list.add(1); list.add(2); // 方法一調用正常 System.out.println(sort(list).getClass()); // 方法二調用報錯了,這裏報錯了 System.out.println(sort2(list).getClass()); } }
這裏彷佛又不太同樣,變成了<T extends Comparable<T>>
,實際上是同樣的啦,若是單獨寫<T>
是至關於<T extends Object>
的。
那麼,咱們就延伸一下,被調用者是徹底無感的,它只能盡力拿到它知道的類型,好比這裏就只能盡力拿到Comparable,若是是<T>
拿到的就是Object。
因此,方法二返回的就是實打實的Comparable[]類型,做爲被調用者,它一點問題都沒有。
可是,調用方是知道我須要的是Integer[]類型的,由於list裏面是Integer類型,因此返回的應該是Integer[]類型,因此我就強轉嘍,而後就報錯了。
究竟是不是這樣?咱們來看看反編譯後的代碼:
// GenericTest.class(反編譯) public class GenericTest { public GenericTest() { } public static <T extends Comparable<T>> List<T> sort(List<T> list) { return Arrays.asList(list.toArray((Comparable[])(new Comparable[list.size()]))); } public static <T extends Comparable<T>> T[] sort2(List<T> list) { // 這裏使用的是Comparable[]強轉,因此返回的也是實打實的Comparable[]類型 return (Comparable[])list.toArray((Comparable[])(new Comparable[list.size()])); } public static void main(String[] args) { List<Integer> list = new ArrayList(); list.add(1); list.add(2); System.out.println(sort(list).getClass()); // 數組向下轉型失敗 System.out.println(((Integer[])sort2(list)).getClass()); } }
能夠看到,跟咱們的分析徹底一致。
java中的泛型只在編譯期有效,在運行時只有調用者知道它本身須要什麼類型,且調用者調用泛型方法後本身作強制轉換,被調用者是徹底無感的,被調用者只能盡力拿到它所知道的類型。
此時,個人腦海中不經響起那熟悉的旋律,「一句話,一生……」,今天的這句話你記住了嗎?