簡單理解java泛型的本質(zhì)(非類型擦除)
背景
之前在網(wǎng)上發(fā)現(xiàn)這個(gè)問題
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) { // 這里沒報(bào)錯(cuò) 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); // 方法一調(diào)用正常 System.out.println(sort(list).getClass()); // 方法二調(diào)用報(bào)錯(cuò)了,這里報(bào)錯(cuò)了 System.out.println(sort2(list).getClass()); } }
這個(gè)問題有以下四個(gè)現(xiàn)象:
(1)方法一調(diào)用完全正常;
(2)方法二調(diào)用報(bào)錯(cuò)了;
(3)方法二報(bào)錯(cuò)的地方是在System.out.println(sort2(list).getClass());這行,而不是return list.toArray((T[]) new Comparable[list.size()]);這行;
(4)報(bào)的錯(cuò)是[Ljava.lang.Comparable; cannot be cast to [Ljava.lang.Integer;;
怎么樣?你心中有答案嘛?類型擦除?怎么擦?摩擦摩擦?
解決
剛拿到這道題,我也是一臉懵逼,這要報(bào)錯(cuò)也應(yīng)該是在return list.toArray((T[]) new Comparable[list.size()]);這行啊,而且要報(bào)錯(cuò)應(yīng)該兩個(gè)方法都報(bào)錯(cuò)啊。
抱著不放棄不拋棄的心態(tài),彤哥做了大量的實(shí)驗(yàn),終于得出了泛型的本質(zhì),且聽我娓娓道來。
小插曲
首先,我們要明白,java中的數(shù)組是不支持向下轉(zhuǎn)型的,但是如果本身就是那個(gè)類型的是可以轉(zhuǎn)過去的,請(qǐng)看下面的例子:
public static void main(String[] args) { Object[] objs = new Object[]{1}; // 類型轉(zhuǎn)換錯(cuò)誤 // Integer[] ins = (Integer[]) objs; Object[] objs2 = new Integer[]{1}; // 不報(bào)錯(cuò) Integer[] ins2 = (Integer[]) objs2; }
java里的泛型是假泛型,只在編譯期有效,在運(yùn)行時(shí)是沒有泛型的概念的,舉個(gè)簡單的例子:
public static void main(String[] args) { List<String> strList = Arrays.asList("1"); List<Integer> intList = Arrays.asList(1); // 打?。簍rue System.out.println(strList.getClass() == intList.getClass()); }
可以看到兩個(gè)list的類型是一樣的,如果你覺得這個(gè)例子不夠說服力,那我給你個(gè)過分點(diǎn)的例子:
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); }
瞧,我可以往一個(gè)String類型的List中扔任何我想扔的東西,服不服?!
所以說java里面的泛型是假的,運(yùn)行時(shí)不存在滴。
回歸正題
數(shù)組不能向下強(qiáng)轉(zhuǎn)我懂了,類型擦除我也懂了,似乎還是過不好這一生,呃不是,是還是解決不了這道題啊?
呃,好像是~~
我們?cè)賮砜匆粋€(gè)簡單的例子:
// 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; } }
嗯~似乎看出來點(diǎn)端倪,反編譯后多了個(gè)構(gòu)造方法。
呃,沒錯(cuò)。還有呢?
仔細(xì)一看,System.out.println((String)raw("1"));這一句多加了個(gè)String強(qiáng)轉(zhuǎn)。
這就是關(guān)鍵所在,結(jié)合類型擦除,運(yùn)行時(shí)并沒有所謂的泛型,所以raw()返回的其實(shí)是Object,但是調(diào)用者自己知道我要的是String類型啊,所以我就知道強(qiáng)轉(zhuǎn)一下嘍。
我們?cè)賮砜磦€(gè)極端的例子:
// 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); } }
仔細(xì)觀察,可以發(fā)現(xiàn),raw()方法里的強(qiáng)轉(zhuǎn)(T)new Integer(1)變成了new Integer(1),強(qiáng)轉(zhuǎn)被擦除了,實(shí)際上在運(yùn)行時(shí)這里的T變成了Object,所有類型都是Object的子類,也就不需要強(qiáng)轉(zhuǎn)了。
而(String)raw("1")的強(qiáng)轉(zhuǎn)還是加上的,這是調(diào)用者知道類型是String,所以raw()返回后自己強(qiáng)轉(zhuǎn)成String一下。
當(dāng)然,這個(gè)代碼運(yùn)行是會(huì)報(bào)錯(cuò)的,java.lang.Integer cannot be cast to java.lang.String,因?yàn)閞aw()返回的是Integer類型,強(qiáng)轉(zhuǎn)成String類型失敗了。
好了,基本思路就是這樣。
泛型類呢?
我們上面舉的例子都是泛型方法,那么泛型類呢?
同樣地,我們來看個(gè)例子:
// 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); } }
可以看到,跟泛型方法的表現(xiàn)一模一樣。當(dāng)然,這里運(yùn)行時(shí)也會(huì)報(bào)java.lang.Integer cannot be cast to java.lang.String這個(gè)錯(cuò)誤。
總結(jié)
java中的泛型只在編譯期有效,在運(yùn)行時(shí)只有調(diào)用者知道需要什么類型,且調(diào)用者調(diào)用泛型方法后自己做強(qiáng)制轉(zhuǎn)換,被調(diào)用者是完全無感的。
所以,出現(xiàn)問題不要問被調(diào)用者,而是要問調(diào)用者,你丫是怎么調(diào)用的?!
解答開篇
為了方便我們還是把開篇的問題拿過來。
// 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) { // 這里沒報(bào)錯(cuò) 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); // 方法一調(diào)用正常 System.out.println(sort(list).getClass()); // 方法二調(diào)用報(bào)錯(cuò)了,這里報(bào)錯(cuò)了 System.out.println(sort2(list).getClass()); } }
這里似乎又不太一樣,變成了<T extends Comparable<T>>,其實(shí)是一樣的啦,如果單獨(dú)寫<T>是相當(dāng)于<T extends Object>的。
那么,我們就延伸一下,被調(diào)用者是完全無感的,它只能盡力拿到它知道的類型,比如這里就只能盡力拿到Comparable,如果是<T>拿到的就是Object。
所以,方法二返回的就是實(shí)打?qū)嵉腃omparable[]類型,作為被調(diào)用者,它一點(diǎn)問題都沒有。
但是,調(diào)用方是知道我需要的是Integer[]類型的,因?yàn)閘ist里面是Integer類型,所以返回的應(yīng)該是Integer[]類型,所以我就強(qiáng)轉(zhuǎn)嘍,然后就報(bào)錯(cuò)了。
到底是不是這樣?我們來看看反編譯后的代碼:
// 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[]強(qiáng)轉(zhuǎn),所以返回的也是實(shí)打?qū)嵉腃omparable[]類型 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()); // 數(shù)組向下轉(zhuǎn)型失敗 System.out.println(((Integer[])sort2(list)).getClass()); } }
可以看到,跟我們的分析完全一致。
java中的泛型只在編譯期有效,在運(yùn)行時(shí)只有調(diào)用者知道它自己需要什么類型,且調(diào)用者調(diào)用泛型方法后自己做強(qiáng)制轉(zhuǎn)換,被調(diào)用者是完全無感的,被調(diào)用者只能盡力拿到它所知道的類型。
此時(shí),我的腦海中不經(jīng)響起那熟悉的旋律,“一句話,一輩子……”,今天的這句話你記住了嗎?
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Mybatis中強(qiáng)大的resultMap功能介紹
這篇文章主要給大家介紹了關(guān)于Mybatis中強(qiáng)大的resultMap功能的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Mybatis具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-06-06mybatis中resultMap 標(biāo)簽的使用教程
resultMap 標(biāo)簽用來描述如何從數(shù)據(jù)庫結(jié)果集中來加載對(duì)象,這篇文章重點(diǎn)給大家介紹mybatis中resultMap 標(biāo)簽的使用,感興趣的朋友一起看看吧2018-07-07java 8 lambda表達(dá)式list操作分組、過濾、求和、最值、排序、去重代碼詳解
java8的lambda表達(dá)式提供了一些方便list操作的方法,主要涵蓋分組、過濾、求和、最值、排序、去重,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2024-01-01

java數(shù)字和中文算數(shù)驗(yàn)證碼的實(shí)現(xiàn)

java數(shù)據(jù)類型與二進(jìn)制詳細(xì)介紹