一句話,講清楚java泛型的本質(zhì)(非類型擦除)-創(chuàng)新互聯(lián)

背景

昨天,在逛論壇時(shí)遇到個(gè)這么個(gè)問題,上代碼:

創(chuàng)新互聯(lián)于2013年開始,是專業(yè)互聯(lián)網(wǎng)技術(shù)服務(wù)公司,擁有項(xiàng)目做網(wǎng)站、成都網(wǎng)站制作網(wǎng)站策劃,項(xiàng)目實(shí)施與項(xiàng)目整合能力。我們以讓每一個(gè)夢(mèng)想脫穎而出為使命,1280元從江做網(wǎng)站,已為上家服務(wù),為從江各地企業(yè)和個(gè)人服務(wù),聯(lián)系電話:13518219792
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è)簡(jiǎn)單的例子:

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è)簡(jiǎn)單的例子:

// 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());
    }
}

這里似乎又不太一樣,變成了&lt;T extends Comparable&lt;T&gt;&gt;,其實(shí)是一樣的啦,如果單獨(dú)寫&lt;T&gt;是相當(dāng)于&lt;T extends Object&gt;的。

那么,我們就延伸一下,被調(diào)用者是完全無感的,它只能盡力拿到它知道的類型,比如這里就只能盡力拿到Comparable,如果是&lt;T&gt;拿到的就是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)響起那熟悉的旋律,“一句話,一輩子……”,今天的這句話你記住了嗎?


一句話,講清楚java泛型的本質(zhì)(非類型擦除)

另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時(shí)售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國(guó)服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡(jiǎn)單易用、服務(wù)可用性高、性價(jià)比高”等特點(diǎn)與優(yōu)勢(shì),專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場(chǎng)景需求。

分享文章:一句話,講清楚java泛型的本質(zhì)(非類型擦除)-創(chuàng)新互聯(lián)
轉(zhuǎn)載來于:http://bm7419.com/article0/hcdio.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供品牌網(wǎng)站制作、網(wǎng)站設(shè)計(jì)、網(wǎng)站導(dǎo)航、網(wǎng)頁設(shè)計(jì)公司、定制開發(fā)外貿(mào)網(wǎng)站建設(shè)

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

h5響應(yīng)式網(wǎng)站建設(shè)