欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

簡單理解java泛型的本質(非類型擦除)

 更新時間:2019年05月24日 10:01:59   作者:彤哥讀源碼  
泛型在java中有很重要的地位,在面向對象編程及各種設計模式中有非常廣泛的應用。泛型是參數(shù)化類型的應用,操作的數(shù)據(jù)類型不限定于特定類型,可以根據(jù)實際需要設置不同的數(shù)據(jù)類型,以實現(xiàn)代碼復用。下面小編來簡單講一講泛型

背景

之前在網(wǎng)上發(fā)現(xiàn)這個問題

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

這個問題有以下四個現(xiàn)象:

(1)方法一調用完全正常;

(2)方法二調用報錯了;

(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;;

怎么樣?你心中有答案嘛?類型擦除?怎么擦?摩擦摩擦?

解決

剛拿到這道題,我也是一臉懵逼,這要報錯也應該是在return list.toArray((T[]) new Comparable[list.size()]);這行啊,而且要報錯應該兩個方法都報錯啊。

抱著不放棄不拋棄的心態(tài),彤哥做了大量的實驗,終于得出了泛型的本質,且聽我娓娓道來。

小插曲

首先,我們要明白,java中的數(shù)組是不支持向下轉型的,但是如果本身就是那個類型的是可以轉過去的,請看下面的例子:

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);
 // 打?。簍rue
 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里面的泛型是假的,運行時不存在滴。

回歸正題

數(shù)組不能向下強轉我懂了,類型擦除我也懂了,似乎還是過不好這一生,呃不是,是還是解決不了這道題?。?/p>

呃,好像是~~

我們再來看一個簡單的例子:

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

仔細觀察,可以發(fā)現(xiàn),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);
 }
}

可以看到,跟泛型方法的表現(xiàn)一模一樣。當然,這里運行時也會報java.lang.Integer cannot be cast to java.lang.String這個錯誤。

總結

java中的泛型只在編譯期有效,在運行時只有調用者知道需要什么類型,且調用者調用泛型方法后自己做強制轉換,被調用者是完全無感的。

所以,出現(xiàn)問題不要問被調用者,而是要問調用者,你丫是怎么調用的?!

解答開篇

為了方便我們還是把開篇的問題拿過來。

// 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());
 // 數(shù)組向下轉型失敗
 System.out.println(((Integer[])sort2(list)).getClass());
 }
}

可以看到,跟我們的分析完全一致。

java中的泛型只在編譯期有效,在運行時只有調用者知道它自己需要什么類型,且調用者調用泛型方法后自己做強制轉換,被調用者是完全無感的,被調用者只能盡力拿到它所知道的類型。

此時,我的腦海中不經(jīng)響起那熟悉的旋律,“一句話,一輩子……”,今天的這句話你記住了嗎?

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。

相關文章

  • SpringBoot使用OpenCV示例總結

    SpringBoot使用OpenCV示例總結

    這篇文章主要介紹了SpringBoot使用OpenCV示例總結,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-08-08
  • Mybatis中強大的resultMap功能介紹

    Mybatis中強大的resultMap功能介紹

    這篇文章主要給大家介紹了關于Mybatis中強大的resultMap功能的相關資料,文中通過示例代碼介紹的非常詳細,對大家學習或者使用Mybatis具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧
    2019-06-06
  • 排序算法圖解之Java快速排序的分步刨析

    排序算法圖解之Java快速排序的分步刨析

    快速排序是通過一趟排序將要排序的數(shù)據(jù)分割為獨立的兩個部分,一部分的所有數(shù)據(jù)比另外一部分的所有數(shù)據(jù)要小,然后按照此方法對這兩部分分別進行快速排序,整個過程可以遞歸進行,以此達到整個數(shù)據(jù)變成有序序列。本文通過示例講解了快速排序的實現(xiàn),需要的可以參考一下
    2022-11-11
  • idea中JRebel不生效問題及解決方案

    idea中JRebel不生效問題及解決方案

    這篇文章主要介紹了idea中JRebel不生效問題及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2023-03-03
  • Java自定義Enum的實現(xiàn)示例

    Java自定義Enum的實現(xiàn)示例

    Java中的自定義Enum類型是一種特殊的類,用于表示固定數(shù)量的常量值,本文主要介紹了Java自定義Enum的實現(xiàn)示例,具有一定的參考價值,感興趣的可以了解一下
    2023-12-12
  • mybatis中resultMap 標簽的使用教程

    mybatis中resultMap 標簽的使用教程

    resultMap 標簽用來描述如何從數(shù)據(jù)庫結果集中來加載對象,這篇文章重點給大家介紹mybatis中resultMap 標簽的使用,感興趣的朋友一起看看吧
    2018-07-07
  • java 8 lambda表達式list操作分組、過濾、求和、最值、排序、去重代碼詳解

    java 8 lambda表達式list操作分組、過濾、求和、最值、排序、去重代碼詳解

    java8的lambda表達式提供了一些方便list操作的方法,主要涵蓋分組、過濾、求和、最值、排序、去重,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友參考下吧
    2024-01-01
  • java簡單實現(xiàn)數(shù)組中的逆序對

    java簡單實現(xiàn)數(shù)組中的逆序對

    這篇文章主要為大家詳細介紹了java簡單實現(xiàn)數(shù)組中的逆序對,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-03-03
  • java數(shù)字和中文算數(shù)驗證碼的實現(xiàn)

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

    這篇文章主要介紹了java數(shù)字和中文算數(shù)驗證碼的實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-07-07
  • java數(shù)據(jù)類型與二進制詳細介紹

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

    這篇文章主要介紹了java數(shù)據(jù)類型與二進制詳細介紹的相關資料,這里對數(shù)據(jù)類型進行了一一介紹分析,并說明自動轉換和強制轉換,需要的朋友可以參考下
    2017-07-07

最新評論