public class Pair<T> { private T first; private T second; public Pair() { first = null; second = null; } public Pair(T first, T second){ this.first = first; this.second = second; } public T getFirst(){ return first; } public T getSecond(){ return second; } public void setFirst(T first) { this.first = first; } public void setSecond(T second) { this.second = second; } }
public class Pair<T, U> {....}
注: 類型變量的定義須要必定的規範:
(1) 類型變量使用大寫形式,而且要比較短;
(2)常見的類型變量特別表明一些意義:變量E 表示集合類型,K和V表示關鍵字和值的類型;T、U、S表示任意類型;java
例如: private T first;數組
例如: Pair<String> 表明將上述全部的T 都替換成了String
public class TestUtils { public static <T> T getMiddle(T... a){ return a[a.length / 2]; } }
String middle = TestUtils.<String>getMiddle("a", "b", "c");
若是上圖這種狀況其實能夠省略
String middle = TestUtils.getMiddle("a", "b", "c");
可是若是是如下調用可能會有問題:
如圖:能夠看到變意思沒有辦法肯定這裏的類型,由於此時咱們入參傳遞了一個Double3.14
兩個Integer1729
和0
編譯器認爲這三個不屬於同一個類型;
此時有一種解決辦法就是把整型寫成Double類型
函數
計算數組中最下的元素
this
compareTo
這個函數,因此這麼能讓編譯器相信咱們這個T是必定會有compareTo
呢?<T extends Comparable>
這裏的意思是T必定是繼承Comparable
的類Comparable
是必定有compareTo
這個方法,因此T必定有compareTo
方法,因而編譯器就不會報錯了min
這個方法也只有繼承了Comparable
的類才能夠調用;extends
關鍵字並用&
分割如:T extends Comparable & Serializable
&
分割的,逗號來分割多個類型變量<T extends Comparable & Serializable , U extends Comparable>
不論何時定義一個泛型類型,虛擬機都會提供一個相應的原始類型(raw type)。原始類型的名字就是刪掉類型參數後的泛型類型。擦除類型變量,並替換限定類型(沒有限定類型的變量用Object)翻譯
列如: Pair
的原始類型以下所示 code
public class Pair { private Object first; private Object second; public Pair() { first = null; second = null; } public Pair(Object first, Object second){ this.first = first; this.second = second; } public Object getFirst(){ return first; } public Object getSecond(){ return second; } public void setFirst(Object first) { this.first = first; } public void setSecond(Object second) { this.second = second; } }
public class Interval<T extends Comparable & Serializable> implements Serializable{ private T lower; private T upper; }
public class Interval implements Serializable{ private Comparable lower; private Comparable upper; }
例如:對象
Pair<Employee> buddies = ...; Employee buddy = buddies.getFirst();
咱們能夠反編譯驗證一下
blog
關鍵的字節碼有如下兩條
9: invokevirtual #4 // Method com/canglang/Pair.getFirst:()Ljava/lang/Object;
12: checkcast #5 // class com/canglang/model/Employee
繼承
虛擬機指令含義以下:
- invokevirtual:虛函數調用,調用對象的實例方法,根據對象的實際類型進行派發,支持多態;
- checkcast:用於檢查類型強制轉換是否能夠進行。若是能夠進行,checkcast指令不會改變操做數棧,不然它會拋出ClassCastException異常;
由此咱們能夠驗證了上述的結論,在反編譯後的字節碼中看到,當對泛型表達式調用時,虛擬機操做以下:
類型擦除也會出如今泛型方法裏面
public static <T extends Comparable> T min(T[] a)
類型擦除後
public static Comparable min(Comparable[] a)
此時能夠看到類型參數T已經被擦除了,只剩下限定類型Comparable;
方法的類型擦除帶來了兩個複雜的問題,看下面的示例:
public class DateInterval extends Pair<LocalDate> { public void setSecond(LocalDate second){ System.out.println("DateInterval: 進來這裏了!"); } }
此時有個問題,從Pair繼承的setSecond方法類型擦除後爲
public void setSecond(Object second)
這個和DateInterval的setSecond明顯是兩個不一樣的方法,由於他們有不一樣的類型的參數,一個是Object,一個LocalDate;
那麼看下面一個列子
public class Test { public static void main(String[] args) { DateInterval interval = new DateInterval(); Pair<LocalDate> pair = interval; pair.setSecond(LocalDate.of(2020, 5, 20)); } }
Pair引用了DateInterval對象,因此應該調用DateInterval.setSecond。
咱們看一下運行結果
可是看了反編譯的字節碼可能發現一個問題:
17: invokestatic #4 // Method java/time/LocalDate.of:(III)Ljava/time/LocalDate;
20: invokevirtual #5 // Method com/canglang/Pair.setSecond:(Ljava/lang/Object;)V
這裏能夠看到此處字節碼調用的是Pair.setSecond
這裏有個重要的概念就是橋方法
引用Oracle中對於這個現象的解釋
爲了解決此問題並在類型擦除後保留通用類型的 多態性,
Java編譯器生成了一個橋接方法,以確保子類型可以按預期工做。
對於DateInterval類,編譯器爲setSecond生成如下橋接方法:
public class DateInterval extends Pair { // Bridge method generated by the compiler // public void setSecond(Object second) { setSecond((LocalDate)second); } public void setSecond(LocalDate second){ System.out.println("DateInterval: 進來這裏了!"); } }
那麼咱們如何驗證是否生成這個橋方法呢?咱們能夠反編譯一下DateInterval.java看一下字節碼;
public void setSecond(java.lang.Object);
descriptor: (Ljava/lang/Object;)V
flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
Code:
stack=2, locals=2, args_size=2
0: aload_0
1: aload_1
2: checkcast #5 // class java/time/LocalDate
5: invokevirtual #6 // Method setSecond:(Ljava/time/LocalDate;)V
8: return
我截取了部分發如今 DateInterval的字節碼中的確會有一個橋方法,同時驗證了上面的問題;
總結: