天天看點

Java 泛型簡單總結實踐:<? extends T>和<? super T>

概念:

  • 泛型的本質是參數化類型

泛型實質

  • 隻在編譯階段有效。在編譯後,将泛型資訊擦出,添加類型檢查和類型轉換
  • 作為文法糖對于JVM是透明,有泛型的和沒有泛型的代碼,編譯生成的二進制代碼是相同的。

泛型的作用

  • - 更加靈活通用
  • - 安全性檢查提前到編譯期
  • 省去類型強制轉換

類型擦除

  • 運作過程中将具體的類型都抹除。
  • 使用泛型加上類型參數,編譯會去掉,生成的位元組碼不包含類型資訊
  • Java不能實作真正的泛型,隻能使用類型擦除來實作僞泛型

類型擦除後原始類型:

  • 類型擦除後,使用限定類型,無限定用Object。

Q:類型變量在編譯的時候擦除掉, 往 ArrayList 建立的對象中添加整數會報錯

  • 先檢查泛型的類型,在進行類型擦除,再進行編譯。

類型檢查對象

  • 類型檢查針對引用的,對引用調用的泛型方法進行類型檢測,而無關真正引用的對象。
  • 沒有引用 ,類型是對象類型

泛型參數不考慮繼承關系

  • ArrayList<String> list1 = new ArrayList<Object>(); //編譯錯誤

3-2.自動類型轉換

  • 存取泛型域時會自動插入強制類型轉換

3-3.類型擦除與多态的沖突和解決方法

  • 類型擦除後,父類的的泛型類型全部變為了原始類型

    Object

  • 編譯器生成橋方法,調用重寫方法。

3-6.泛型在靜态方法和靜态類中的問題

  • 泛型類中的T,靜态方法不能使用
  • 靜态方法定義的T,方法内可以使用

instanceof

  • 運作時指出對象是否是特定類的一個執行個體

類型擦除後使用:

  • 泛型的類型參數隻能是類類型,(類型擦除後,為

    Object

    ,不能存儲

    double

    值)
  • 不能對泛型類型使用instanceof操作(類型擦除後隻剩下原始類型,不能使用instanceof)

泛型數組

  • java中是”不能建立一個确切的泛型類型的數組”
  • 數組的類型不可以是類型變量,除非是采用通配符的方式
  • 數組是協變的,

    Integer[]

    可以轉換為

    Object[]

Java為什麼不能建立泛型數組

  • 泛型擦除後,運作時能添加任何類型

java泛型中<?>和<T>有什麼差別?

  • T 代表一種類型
  • ?是通配符,泛指所有類型,看成所有類型的父類,真實的類型,類型實參

T和?運用的地方有點不同

  • ?是定義在引用變量上,指向多個對象。
  • T是類上或方法上

List<T>是泛型方法,List<?>是限制通配符

List<T>一般有兩種用途:

  • 1、定義通用的泛型方法。
  • 2、限制方法參數和傳回結果的關系。

List<?>一般就是在泛型起一個限制作用。

  1. 類型參數“<T>”,聲明泛型類或泛型方法。
  2. 通配符“<?>”,使用泛型類或泛型方法(因為定義在引用變量上,指向多個對象。)

運用

  • 如果有泛型方法和非泛型方法,都滿足條件,會執行非泛型方法

泛型三種:

  •           [1]ArrayList<T> al=new ArrayList<T>();指定集合元素隻能是T類型
  •           [2]ArrayList<?> al=new ArrayList<?>();集合元素可以是任意類型,這種沒有意義,一般是方法中,隻是為了說明用法
  •           [3]ArrayList<? extends E> al=new ArrayList<? extends E>();

上下界

泛型與向上轉型的概念

  • 協變:子類能向父類轉換 
  • 逆變: 父類能向子類轉換 

    C

上界:extends

  • 指定類型是子類
  • 上邊界,即傳入的類型實參必須是指定類型的子類型
  • <任意字元 extends 類/接口>表示泛型的上限。
  • T extends A    指傳入實參類型T  必須是A或A的子類型
  • ? extends T    指實參類型?必須是T或T的子類型

下界: super

  • 指定類型是父類
  • <任意字元 super 類/接口>表示泛型的下限。

上下界使用

  • 上界<? extends T>不能往裡存,隻能往外取
  •  下界<? super T>不影響往裡存,但往外取隻能放在Object對象裡

<T extends Comparable<? super T>>

  • extends對泛型上限進行了限制即T必須是Comparable<? super T>的子類,然後<? super T>表示Comparable<>中的類型下限為T!

2.  

<T extends Comparable<T>>

 和 

<T extends Comparable<? super T>>

 有什麼不同

<T extends Comparable<T>>

  • 類型T必須實作

    Comparable

    接口,并且這個接口的類型是T,這樣,T的執行個體之間才能互相比較大小。

<T extends Comparable<? super T>> 

  • 類型T必須實作

    Comparable

    接口,并且這個接口的類型是T或者是T的任一父類。這樣聲明後,T的執行個體之間和T的父類的執行個體之間可以互相比較大小。

使用

在調用泛型方法時,可以指定泛型,也可以不指定泛型。

  • 不指定泛型,該方法的幾種類型的同一父類的最小級,直到Object
  • 在指定泛型的情況下,該方法的幾種類型必須是該泛型的執行個體的類型或者其子類

泛型方法指定類型: ArrayAlg.<String>getMiddle(names);  

實作

  • 當實作泛型接口的類,未傳入泛型實參時,将泛型的聲明也一起加到類中
  • 泛型方法,聲明了<T>的方法才是泛型方法

代碼關鍵使用了解:

上下界使用了解

1 為什麼要用通配符和邊界?

  • Plate<Fruit> p=new Plate<Apple>(new Apple()); //錯誤
  • 原因:容器裝的東西有繼承,但容器沒有繼承。

上界:

  • 能放水果以及水果派生類的盤子,

    Plate<? extends Fruit>

    Plate<Fruit>

    以及

    Plate<Apple>

    的基類。

下界:

  • 能放水果以及水果基類的盤子。

    Plate<? super Fruit>

    Plate<Fruit>,Plate<Food>

    的基類

上界<? extends T>不能往裡存,隻能往外取的了解

  • Plate<? extends Fruit> p=new Plate<Apple>(new Apple());
  • 編譯器在類型Plate指派後。隻标上占位符:CAP#1,具體類不知道,插入對象編譯器不知道是否比對,是以都不允許。
  • 插入類可能不是編譯類就,但有同一父類,
  • 讀取時可以轉化為相應類
  • 放東西的set( )方法失效。但取東西get( )方法有效

T都代表同一種類型

  • public <T> List<T> fill(T... t);

?是通配符,泛指所有類型,看成所有類型的父類,真實的類型,類型實參

  • Plate<?>

    單純的就表示:盤子裡放了一個東西,是什麼我不知道。

總結

  • Plate<? extends Fruit>

    裡什麼都放不進去。

 下界<? super T>不影響往裡存,但往外取隻能放在Object對象裡的了解

  • Plate<? super Fruit> p=new Plate<Fruit>(new Fruit());
  • 下界規定了元素的最小粒度的下限,實際上是放松了容器元素的類型控制。
  • 既然元素是Fruit的基類,那往裡存粒度比Fruit小的都可以。
  • 但往外讀取元素就費勁了,隻有所有類的基類Object對象才能裝下。但這樣的話,元素的類型資訊就全部丢失。

PECS(Producer Extends Consumer Super)原則

  • 頻繁往外讀取内容的,适合用上界Extends。
  • 經常往裡插入的,适合用下界Super。

Java擷取泛型T的類型 T.class

泛型執行個體化後可以擷取具體類型

  • ((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];

總結:

  • 傳回類型   getClass
  • 傳回直接繼承的父類(或者泛型T的具體類型)        getGenericSuperclass
  • 參數化類型  ParameterizedType
  • 擷取第一個泛型參數的類型類  getActualTypeArguments()[0]

Class<Integer> cla;與Class<?> cl;

  • 前一個表示cla隻能指向Integer這種類型,而後一個cl表示可以指向任意類型。

代碼:

泛型的本質是參數化類型

也就是說,泛型就是将所操作的資料類型作為參數的一種文法。

public class Paly<T>{
    T play(){}
}
           

其中

T

就是作為一個類型參數在

Play

被執行個體化的時候所傳遞來的參數,比如:

Play<Integer> playInteger=new Play<>();
           

當實作泛型接口的類

  1. 未傳入泛型實參時,與泛型類的定義相同,在聲明類的時候,需将泛型的聲明也一起加到類中
  • class FruitGenerator<T> implements Generator<T>

’?’是類型實參,而不是類型形參 ,可以把?看成所有類型的父類。是一種真實的類型。

  • showKeyValue1(Generic<?> obj

泛型方法,聲明了<T>的方法才是泛型方法

  • public <T> T genericMethod(Class<T> tClass)

泛型方法與可變參數

  • public <T> void printMsg( T... args)

泛型數組

  • java中是”不能建立一個确切的泛型類型的數組”
  • 數組的類型不可以是類型變量,除非是采用通配符的方式

也就是說下面的這個例子是不可以的:

List<String>[] ls = new ArrayList<String>[10];  
           

而使用通配符建立泛型數組是可以的,如下面這個例子:

List<?>[] ls = new ArrayList<?>[10]; 
           

這樣也是可以的:

List<String>[] ls = new ArrayList[10];
           

java泛型中<?>和<T>有什麼差別?

T 代表一種類型

?是通配符,泛指所有類型

  • 一般定義引用變量,這麼做的好處是,如下所示,定義一個sup的引用變量,就可以指向多個對象。

好處:

  • SuperClass<?> sup = new SuperClass<String>("lisi");
  • sup = new SuperClass<People>(new People());
  • sup = new SuperClass<Animal>(new Animal());

1

  • 如果有泛型方法和非泛型方法,都滿足條件,會執行非泛型方法
  • 加了泛型了參數不能調用與參數類型有關的方法

代碼:

  • public static void printCollecton(Collection <?> collection)
  • for(Object obj: collection)

List<T>是泛型方法,List<?>是限制通配符

List<T>一般有兩種用途:

  • 1、定義一個通用的泛型方法。
  • 2、限制方法的參數之間或參數和傳回結果之間的關系。

代碼:

  • List<T> getList<T param1,T param2>

extends

  • 如<任意字元 extends 類/接口>表示泛型的上限。

代碼

  • class Demo<T extends List>{}
  •   Demo<ArrayList> p = null; // 編譯正确

<T extends Comparable<? super T>>

  • extends對泛型上限進行了限制即T必須是Comparable<? super T>的子類,然後<? super T>表示Comparable<>中的類型下限為T!

2.  

<T extends Comparable<T>>

 和 

<T extends Comparable<? super T>>

 有什麼不同

<T extends Comparable<T>>

  • 類型T必須實作

    Comparable

    接口,并且這個接口的類型是T,這樣,T的執行個體之間才能互相比較大小。

<T extends Comparable<? super T>> 

  • 類型T必須實作

    Comparable

    接口,并且這個接口的類型是T或者是T的任一父類。這樣聲明後,T的執行個體之間和T的父類的執行個體之間可以互相比較大小。

Java擷取泛型T的類型 T.class

泛型執行個體化後可以擷取具體類型

  • ((ParameterizedType)this.getClass().getGenericSuperclass()).getActualTypeArguments()[0];

總結:

  • 傳回類型   getClass
  • 傳回直接繼承的父類(或者泛型T的具體類型)        getGenericSuperclass
  • 參數化類型  ParameterizedType
  • 擷取第一個泛型參數的類型類  getActualTypeArguments()[0]

實踐:

  • public static <T extends Comparable<? super T>> void mySort2(List<T> l)
  •  mySort2(animals);
  •     mySort2(dogs);

這裡接口類型:

  •  泛型的參數類型。

類型擦除

  • 使用泛型的時候加上類型參數,在編譯器編譯的時候會去掉,生成的位元組碼中是不包含泛型中的類型資訊的

總結

  • 使用泛型的時候加上類型參數,在編譯器編譯的時候會去掉,

1-2.通過兩個例子證明Java類型的類型擦除

  • ArrayList<String> list1 = new ArrayList<String>(); list1.add("abc");
  • ArrayList<Integer> list2 = new ArrayList<Integer>(); list2.add(123);
  • System.out.println(list1.getClass() == list2.getClass());

結論:

  • true

    。說明泛型類型

    String

    Integer

    都被擦除掉了,隻剩下原始類型。

例2.通過反射添加其它類型元素

  • ArrayList<Integer> list = new ArrayList<Integer>(); list.add(1); 
  •  list.getClass().getMethod("add", Object.class).invoke(list, "asd");
  • 可以存儲字元串,這說明了

    Integer

    泛型執行個體在編譯之後被擦除掉了

總結:

  • 數組的泛型無論添加還是反射最後都是object

2.類型擦除後保留的原始類型

  • 就是擦除去了泛型資訊,最後在位元組碼中的類型變量的真正類型
  • 無論何時定義一個泛型,相應的原始類型都會被自動提供,類型變量擦除,并使用其限定類型(無限定的變量用Object)替換。

總結原始類型:

  • 擦除去了泛型資訊,在位元組碼中的類型變量的真正類型,無限定用Object。

代碼:

  • class Pair<T> { private T value;
  • class Pair { private Object value;

在調用泛型方法時,可以指定泛型,也可以不指定泛型。

  • 在不指定泛型的情況下,泛型變量的類型為該方法中的幾種類型的同一父類的最小級,直到Object
  • 在指定泛型的情況下,該方法的幾種類型必須是該泛型的執行個體的類型或者其子類

代碼:

  • int i = Test.add(1, 2); //這兩個參數都是Integer,是以T為Integer類型 Number f = Test.add(1, 1.2);
  • //這兩個參數一個是Integer,以風格是Float,是以取同一父類的最小級,為Number Object o = Test.add(1, "asd"); //
  • 這兩個參數一個是Integer,以風格是Float,是以取同一父類的最小級,為Object
  • int a = Test.<Integer>add(1, 2); //指定了Integer,是以隻能為Integer類型或者其子類
  • int b = Test.<Integer>add(1, 2.2); //編譯錯誤,指定了Integer,不能為Float
  • Number c = Test.<Number>add(1, 2.2); //指定為Number,是以可以為Integer和Float

其實在泛型類中,不指定泛型的時候,也差不多,隻不過這個時候的泛型為

Object

3-1.先檢查,再編譯以及編譯的對象和引用傳遞問題

Q: 既然說類型變量會在編譯的時候擦除掉,那為什麼我們往 ArrayList 建立的對象中添加整數會報錯呢?不是說泛型變量String會在編譯的時候變為Object類型嗎?為什麼不能存别的類型呢?既然類型擦除了,如何保證我們隻能使用泛型變量限定的類型呢?

  • A: Java編譯器是通過先檢查代碼中泛型的類型,然後在進行類型擦除,再進行編譯。

類型變量會在編譯的時候擦除掉,為什麼添加整數會報錯

  • 先檢查代碼中泛型的類型,然後在進行類型擦除,再進行編譯。

代碼:

  • ArrayList<String> list = new ArrayList<String>();
  • list.add("123");
  • list.add(123);//編譯錯誤

類型檢查就是針對引用的,誰是一個引用,用這個引用調用泛型方法,就會對這個引用調用的方法進行類型檢測,而無關它真正引用的對象。

代碼:

  • ArrayList<String> list1 = new ArrayList(); list1.add("1"); //編譯通過 list1.add(1); //編譯錯誤
  •  ArrayList list2 = new ArrayList<String>(); list2.add("1"); //編譯通過 list2.add(1); //編譯通過
  • new ArrayList<String>().add("11"); //編譯通過 new ArrayList<String>().add(22); //編譯錯誤

總結:沒有引用 ,類型是對象類型

泛型中參數話類型為什麼不考慮繼承關系?

  • ArrayList<String> list1 = new ArrayList<Object>(); //編譯錯誤
  • ArrayList<Object> list2 = new ArrayList<String>(); //編譯錯誤

3-2.自動類型轉換

因為類型擦除的問題,是以所有的泛型類型變量最後都會被替換為原始類型。既然都被替換為原始類型,那麼為什麼我們在擷取的時候,不需要進行強制類型轉換呢?

看下

ArrayList.get()

方法:

  • public E get(int index) {
  • return (E) elementData[index]; }

自動

  • 不用自己進行強轉。當存取一個泛型域時也會自動插入強制類型轉換。

3-3.類型擦除與多态的沖突和解決方法

  • 類型擦除後,父類的的泛型類型全部變為了原始類型

    Object

  • 編譯器生成橋方法,調用重寫方法。

代碼

  •  @Override public void setValue(Date value) { super.setValue(value); }
  • @Override public Date getValue() { return super.getValue(); } }

父類原始

  • public T getValue() { return value; }
  • public void setValue(T value) { this.value = value; }

父類變為

  • public Object getValue() { return value; }
  • public void setValue(Object value) { this.value = value; }

3-6.泛型在靜态方法和靜态類中的問題

  • public class Test2<T> { public static T one; //編譯錯誤

泛型參數的執行個體化是在定義對象的時候指定的,而靜态變量和靜态方法不需要使用對象來調用

  • static <T >T show(T one){ //這是正确的

泛型方法中使用的T是自己在方法中定義的 T

總結:

  • 泛型類中的T,靜态方法不能使用
  • 靜态方法定義的T,方法内可以使用

參考具體;

  代碼如下所示:

Java 泛型簡單總結實踐:&lt;? extends T&gt;和&lt;? super T&gt;
import java.util.GregorianCalendar;
class Demo<T extends Comparable<T>>{}
//注意這裡是沒有? super的
public class Test
{
    public static void main(String[] args) {
       Demo<GregorianCalendar> p = null; 
        }
}
           
Java 泛型簡單總結實踐:&lt;? extends T&gt;和&lt;? super T&gt;

  這裡編譯報錯,因為這裡的<T extends Comparable<T>>相當于<GregorianCalendar extends Comparable<GregorianCalendar>>,但是GregorianCalendar中并沒有實作Comparable<GregorianCalendar>,而是僅僅持有從Calendar繼承過來的Comparable<Calendar>,這樣就會因為不在限制範圍内而報錯。

Java 泛型簡單總結實踐:&lt;? extends T&gt;和&lt;? super T&gt;
import java.util.GregorianCalendar;

class Demo<T extends Comparable<? super T>>{}

public class Test1
{
    public static void main(String[] args) {
       Demo<GregorianCalendar> p = null; // 編譯正确
    }
}  
           
Java 泛型簡單總結實踐:&lt;? extends T&gt;和&lt;? super T&gt;

  此時編譯通過,這裡可以了解為<GregorianCalendar extends Comparable<Calendar>>!因為Calendar為GregorianCalendar 的父類并且GregorianCalendar 實作了Comparable<Calendar>,具體可以在API中進行檢視!

  3. 執行個體代碼示範

  代碼如下所示:

Java 泛型簡單總結實踐:&lt;? extends T&gt;和&lt;? super T&gt;
import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
  
 public class Test
 {
     //第一種聲明:簡單,靈活性低
     public static <T extends Comparable<T>> void mySort1(List<T> list)
     {
         Collections.sort(list);
     }
 
     //第二種聲明:複雜,靈活性高
     public static <T extends Comparable<? super T>> void mySort2(List<T> l)
     {
         Collections.sort(list);
     }
 
     public static void main(String[] args)
     {
         //主函數中将分别建立Animal和Dog兩個序列,然後調用排序方法對其進行測試
     //main函數中具體的兩個版本代碼将在下面具體展示

     }
 }
 
 class Animal implements Comparable<Animal>
 {
     protected int age;
 
     public Animal(int age)
 
     {
         this.age = age;
     }
 
     //使用年齡與另一執行個體比較大小
     @Override
     public int compareTo(Animal other)
     {
         return this.age - other.age;
     }
 }
 
 class Dog extends Animal
 {
     public Dog(int age)
     {
         super(age);
     }
 }
           
Java 泛型簡單總結實踐:&lt;? extends T&gt;和&lt;? super T&gt;

上面的代碼包括三個類:

  1. Animal

    實作了

    Comparable<Animal>

    接口,通過年齡來比較執行個體的大小
  2. Dog從Animal繼承,為其子類。
  3. Test

    類中提供了兩個排序方法和測試用的

    main()

    方法:
    • mySort1()

      使用

      <T extends Comparable<T>>

      類型參數
    • mySort2()

      使用

      <T extends Comparable<? super T>>

      類型參數
    • main()

      測試方法。在這裡将分别建立Animal和Dog兩個序列,然後調用排序方法對其進行測試。

  3.1 對mySort1()進行測試,main方法代碼如下所示:

Java 泛型簡單總結實踐:&lt;? extends T&gt;和&lt;? super T&gt;
// 建立一個 Animal List
List<Animal> animals = new ArrayList<Animal>();
animals.add(new Animal(25));
animals.add(new Dog(35));

// 建立一個 Dog List
List<Dog> dogs = new ArrayList<Dog>();
dogs.add(new Dog(5));
dogs.add(new Dog(18));

// 測試  mySort1() 方法
mySort1(animals);
mySort1(dogs);
           
Java 泛型簡單總結實踐:&lt;? extends T&gt;和&lt;? super T&gt;

  結果編譯出錯,報錯資訊為:

The method mySort1(List<T>) in the type TypeParameterTest is not applicable for the arguments (List<Dog>)
           

  mySort1() 方法的類型參數是<T extends Comparable<T>>,它要求的類型參數是類型為T的Comparable。

  如果傳入的是List<Animal>程式将正常執行,因為Animal實作了接口Comparable<Animal>。

  但是,如果傳入的參數是List<Dog>程式将報錯,因為Dog類中沒有實作接口Comparable<Dog>,它隻從Animal繼承了一個Comparable<Animal>接口。

  注意:animals list中實際上是包含一個Dog執行個體的。如果碰上類似的情況(子類list不能傳入到一個方法中),可以考慮把子類執行個體放到一個父類 list 中,避免編譯錯誤。

  3.2 對mySort12()進行測試,main方法代碼如下所示:

Java 泛型簡單總結實踐:&lt;? extends T&gt;和&lt;? super T&gt;
public static void main(String[] args)
{
    // 建立一個 Animal List
    List<Animal> animals = new ArrayList<Animal>();
    animals.add(new Animal(25));
    animals.add(new Dog(35));

    // 建立一個 Dog List
    List<Dog> dogs = new ArrayList<Dog>();
    dogs.add(new Dog(5));
    dogs.add(new Dog(18));

    // 測試  mySort2() 方法
    mySort2(animals);
    mySort2(dogs);
}
           
Java 泛型簡單總結實踐:&lt;? extends T&gt;和&lt;? super T&gt;

  這時候我們發現該程式可以正常運作。它不但能夠接受Animal implements Comparable<Animal>這樣的參數,也可以接收:Dog implements Comparable<Animal>這樣的參數。

  3.3 是否可以通過将Dog實作Comparable<Dog>來解決問題?

  由分析可得程式出現問題是因為Dog類沒有實作接口Comparable<Dog>,那麼我們能否将該類實作接口Comparable<Dog>來解決問題呢?

  代碼如下所示:

Java 泛型簡單總結實踐:&lt;? extends T&gt;和&lt;? super T&gt;
class Dog extends Animal implements Comparable<Dog>
{
    public Dog(int age)
    {
        super(age);
    }
}
           
Java 泛型簡單總結實踐:&lt;? extends T&gt;和&lt;? super T&gt;

  結果程式編譯報錯,錯誤資訊如下所示:

The interface Comparable cannot be implemented more than once with different arguments: Comparable<Animal> and Comparable<Dog>
           

  意義是Dog類已經從Animal中繼承了Comparable該接口,無法再實作一個Comparable。

  若子類需要使用自己的比較方法,則需要重寫父類的public int CompareTo(Animal other)方法。

  4. 總結 

  對Animal/Dog這兩個有父子關系的類來說:

<T extends Comparable<? super T>>

可以接受List<Animal>,也可以接收 List<Dog> 。而

<T extends Comparable<T>>

隻可以接收 List<Animal>是以,<T extends Comparable<? super T>>這樣的類型參數對所傳入的參數限制更少,提高了 API 的靈活性。總的來說,在保證類型安全的前提下,要使用限制最少的類型參數。

1-2.通過兩個例子證明Java類型的類型擦除

例1.原始類型相等

public class Test {

    public static void main(String[] args) {

        ArrayList<String> list1 = new ArrayList<String>();
        list1.add("abc");

        ArrayList<Integer> list2 = new ArrayList<Integer>();
        list2.add(123);

        System.out.println(list1.getClass() == list2.getClass());
    }

}
           

在這個例子中,我們定義了兩個

ArrayList

數組,不過一個是

ArrayList<String>

泛型類型的,隻能存儲字元串;一個是

ArrayList<Integer>

泛型類型的,隻能存儲整數,最後,我們通過

list1

對象和

list2

對象的

getClass()

方法擷取他們的類的資訊,最後發現結果為

true

。說明泛型類型

String

Integer

都被擦除掉了,隻剩下原始類型。

例2.通過反射添加其它類型元素

public class Test {

    public static void main(String[] args) throws Exception {

        ArrayList<Integer> list = new ArrayList<Integer>();

        list.add(1);  //這樣調用 add 方法隻能存儲整形,因為泛型類型的執行個體為 Integer

        list.getClass().getMethod("add", Object.class).invoke(list, "asd");

        for (int i = 0; i < list.size(); i++) {
            System.out.println(list.get(i));
        }
    }

}
           

在程式中定義了一個

ArrayList

泛型類型執行個體化為

Integer

對象,如果直接調用

add()

方法,那麼隻能存儲整數資料,不過當我們利用反射調用

add()

方法的時候,卻可以存儲字元串,這說明了

Integer

泛型執行個體在編譯之後被擦除掉了,隻保留了原始類型。

例3.原始類型Object

class Pair<T> {  
    private T value;  
    public T getValue() {  
        return value;  
    }  
    public void setValue(T  value) {  
        this.value = value;  
    }  
}  
           

Pair的原始類型為:

class Pair {  
    private Object value;  
    public Object getValue() {  
        return value;  
    }  
    public void setValue(Object  value) {  
        this.value = value;  
    }  
}
           

3-3.類型擦除與多态的沖突和解決方法

現在有這樣一個泛型類:

class Pair<T> {  

    private T value;  

    public T getValue() {  
        return value;  
    }  

    public void setValue(T value) {  
        this.value = value;  
    }  
}
           

然後我們想要一個子類繼承它。

class DateInter extends Pair<Date> {  

    @Override  
    public void setValue(Date value) {  
        super.setValue(value);  
    }  

    @Override  
    public Date getValue() {  
        return super.getValue();  
    }  
}
           

在這個子類中,我們設定父類的泛型類型為

Pair<Date>

,在子類中,我們覆寫了父類的兩個方法,我們的原意是這樣的:将父類的泛型類型限定為

Date

,那麼父類裡面的兩個方法的參數都為

Date

類型。

public Date getValue() {  
    return value;  
}  

public void setValue(Date value) {  
    this.value = value;  
}
           

是以,我們在子類中重寫這兩個方法一點問題也沒有,實際上,從他們的

@Override

标簽中也可以看到,一點問題也沒有,實際上是這樣的嗎?

分析:實際上,類型擦除後,父類的的泛型類型全部變為了原始類型

Object

,是以父類編譯之後會變成下面的樣子:

class Pair {  
    private Object value;  

    public Object getValue() {  
        return value;  
    }  

    public void setValue(Object  value) {  
        this.value = value;  
    }  
}  
           

再看子類的兩個重寫的方法的類型:

@Override  
public void setValue(Date value) {  
    super.setValue(value);  
}  
@Override  
public Date getValue() {  
    return super.getValue();  
}
           

先來分析

setValue

方法,父類的類型是

Object

,而子類的類型是

Date

,參數類型不一樣,這如果實在普通的繼承關系中,根本就不會是重寫,而是重載。

  

我們在一個main方法測試一下:

public static void main(String[] args) throws ClassNotFoundException {  
        DateInter dateInter = new DateInter();  
        dateInter.setValue(new Date());                  
        dateInter.setValue(new Object()); //編譯錯誤  
}
           

如果是重載,那麼子類中兩個

setValue

方法,一個是參數

Object

類型,一個是

Date

類型,可是我們發現,根本就沒有這樣的一個子類繼承自父類的Object類型參數的方法。是以說,卻是是重寫了,而不是重載了。

為什麼會這樣呢?

原因是這樣的,我們傳入父類的泛型類型是

Date,Pair<Date>

,我們的本意是将泛型類變為如下:

class Pair {  
    private Date value;  
    public Date getValue() {  
        return value;  
    }  
    public void setValue(Date value) {  
        this.value = value;  
    }  
}
           

然後再子類中重寫參數類型為Date的那兩個方法,實作繼承中的多态。

可是由于種種原因,虛拟機并不能将泛型類型變為

Date

,隻能将類型擦除掉,變為原始類型

Object

。這樣,我們的本意是進行重寫,實作多态。可是類型擦除後,隻能變為了重載。這樣,類型擦除就和多态有了沖突。JVM知道你的本意嗎?知道!!!可是它能直接實作嗎,不能!!!如果真的不能的話,那我們怎麼去重寫我們想要的

Date

類型參數的方法啊。

于是JVM采用了一個特殊的方法,來完成這項功能,那就是橋方法。

首先,我們用

javap -c className

的方式反編譯下

DateInter

子類的位元組碼,結果如下:

class com.tao.test.DateInter extends com.tao.test.Pair<java.util.Date> {  
  com.tao.test.DateInter();  
    Code:  
       0: aload_0  
       1: invokespecial #8                  // Method com/tao/test/Pair."<init>":()V  
       4: return  

  public void setValue(java.util.Date);  //我們重寫的setValue方法  
    Code:  
       0: aload_0  
       1: aload_1  
       2: invokespecial #16                 // Method com/tao/test/Pair.setValue:(Ljava/lang/Object;)V  
       5: return  

  public java.util.Date getValue();    //我們重寫的getValue方法  
    Code:  
       0: aload_0  
       1: invokespecial #23                 // Method com/tao/test/Pair.getValue:()Ljava/lang/Object;  
       4: checkcast     #26                 // class java/util/Date  
       7: areturn  

  public java.lang.Object getValue();     //編譯時由編譯器生成的巧方法  
    Code:  
       0: aload_0  
       1: invokevirtual #28                 // Method getValue:()Ljava/util/Date 去調用我們重寫的getValue方法;  
       4: areturn  

  public void setValue(java.lang.Object);   //編譯時由編譯器生成的巧方法  
    Code:  
       0: aload_0  
       1: aload_1  
       2: checkcast     #26                 // class java/util/Date  
       5: invokevirtual #30                 // Method setValue:(Ljava/util/Date; 去調用我們重寫的setValue方法)V  
       8: return  
}
           

從編譯的結果來看,我們本意重寫

setValue

getValue

方法的子類,竟然有4個方法,其實不用驚奇,最後的兩個方法,就是編譯器自己生成的橋方法。可以看到橋方法的參數類型都是Object,也就是說,子類中真正覆寫父類兩個方法的就是這兩個我們看不到的橋方法。而打在我們自己定義的

setvalue

getValue

方法上面的

@Oveerride

隻不過是假象。而橋方法的内部實作,就隻是去調用我們自己重寫的那兩個方法。

是以,虛拟機巧妙的使用了橋方法,來解決了類型擦除和多态的沖突。

不過,要提到一點,這裡面的

setValue

getValue

這兩個橋方法的意義又有不同。

setValue

方法是為了解決類型擦除與多态之間的沖突。

getValue

卻有普遍的意義,怎麼說呢,如果這是一個普通的繼承關系:

那麼父類的

setValue

方法如下:

public ObjectgetValue() {  
    return super.getValue();  
}
           

而子類重寫的方法是:

public Date getValue() {  
    return super.getValue();  
}
           

其實這在普通的類繼承中也是普遍存在的重寫,這就是協變。

關于協變:。。。。。。

并且,還有一點也許會有疑問,子類中的巧方法

Object getValue()

Date getValue()

是同 時存在的,可是如果是正常的兩個方法,他們的方法簽名是一樣的,也就是說虛拟機根本不能分别這兩個方法。如果是我們自己編寫Java代碼,這樣的代碼是無法通過編譯器的檢查的,但是虛拟機卻是允許這樣做的,因為虛拟機通過參數類型和傳回類型來确定一個方法,是以編譯器為了實作泛型的多态允許自己做這個看起來“不合法”的事情,然後交給虛拟器去差別。

參考:

https://www.cnblogs.com/dengchengchao/p/9717097.html

https://www.cnblogs.com/coprince/p/8603492.html

Java 為什麼不支援建立泛型化數組:https://blog.csdn.net/codejas/article/details/89705168

泛型的上下界

https://www.liangzl.com/get-article-detail-130634.html

java泛型中<?>和<T>有什麼差別?

https://www.cnblogs.com/jpfss/p/9929045.html

Java泛型的應用——T extends Comparable<? super T>

https://www.cnblogs.com/cherryljr/p/6880657.html

Java泛型類型擦除以及類型擦除帶來的問題:

https://www.cnblogs.com/wuqinglong/p/9456193.html

擷取泛型具體類型:

https://blog.csdn.net/hellozhxy/article/details/82024712

<? extends T>和<? super T>

https://www.cnblogs.com/drizzlewithwind/p/6100164.html