概念:
- 泛型的本質是參數化類型
泛型實質
- 隻在編譯階段有效。在編譯後,将泛型資訊擦出,添加類型檢查和類型轉換
- 作為文法糖對于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<?>一般就是在泛型起一個限制作用。
- 類型參數“<T>”,聲明泛型類或泛型方法。
- 通配符“<?>”,使用泛型類或泛型方法(因為定義在引用變量上,指向多個對象。)
運用
- 如果有泛型方法和非泛型方法,都滿足條件,會執行非泛型方法
泛型三種:
- [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必須實作
接口,并且這個接口的類型是T,這樣,T的執行個體之間才能互相比較大小。Comparable
<T extends Comparable<? super T>>
- 類型T必須實作
接口,并且這個接口的類型是T或者是T的任一父類。這樣聲明後,T的執行個體之間和T的父類的執行個體之間可以互相比較大小。Comparable
使用
在調用泛型方法時,可以指定泛型,也可以不指定泛型。
- 不指定泛型,該方法的幾種類型的同一父類的最小級,直到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<>();
當實作泛型接口的類
- 未傳入泛型實參時,與泛型類的定義相同,在聲明類的時候,需将泛型的聲明也一起加到類中
- 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必須實作
接口,并且這個接口的類型是T,這樣,T的執行個體之間才能互相比較大小。Comparable
<T extends Comparable<? super T>>
- 類型T必須實作
接口,并且這個接口的類型是T或者是T的任一父類。這樣聲明後,T的執行個體之間和T的父類的執行個體之間可以互相比較大小。Comparable
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,方法内可以使用
參考具體;
代碼如下所示:

import java.util.GregorianCalendar;
class Demo<T extends Comparable<T>>{}
//注意這裡是沒有? super的
public class Test
{
public static void main(String[] args) {
Demo<GregorianCalendar> p = null;
}
}

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

import java.util.GregorianCalendar;
class Demo<T extends Comparable<? super T>>{}
public class Test1
{
public static void main(String[] args) {
Demo<GregorianCalendar> p = null; // 編譯正确
}
}

此時編譯通過,這裡可以了解為<GregorianCalendar extends Comparable<Calendar>>!因為Calendar為GregorianCalendar 的父類并且GregorianCalendar 實作了Comparable<Calendar>,具體可以在API中進行檢視!
3. 執行個體代碼示範
代碼如下所示:

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

上面的代碼包括三個類:
-
實作了Animal
接口,通過年齡來比較執行個體的大小Comparable<Animal>
- Dog從Animal繼承,為其子類。
-
類中提供了兩個排序方法和測試用的Test
方法:main()
-
使用mySort1()
類型參數<T extends Comparable<T>>
-
使用mySort2()
類型參數<T extends Comparable<? super T>>
-
測試方法。在這裡将分别建立Animal和Dog兩個序列,然後調用排序方法對其進行測試。main()
-
3.1 對mySort1()進行測試,main方法代碼如下所示:

// 建立一個 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);

結果編譯出錯,報錯資訊為:
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方法代碼如下所示:

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

這時候我們發現該程式可以正常運作。它不但能夠接受Animal implements Comparable<Animal>這樣的參數,也可以接收:Dog implements Comparable<Animal>這樣的參數。
3.3 是否可以通過将Dog實作Comparable<Dog>來解決問題?
由分析可得程式出現問題是因為Dog類沒有實作接口Comparable<Dog>,那麼我們能否将該類實作接口Comparable<Dog>來解決問題呢?
代碼如下所示:

class Dog extends Animal implements Comparable<Dog>
{
public Dog(int age)
{
super(age);
}
}

結果程式編譯報錯,錯誤資訊如下所示:
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