引入泛型的主要目標有如下幾點:
類型安全java
- 泛型的主要目標是提升 Java 程序的類型安全
- 編譯時期就能夠檢查出因 Java 類型不正確致使的 ClassCastException 異常
- 符合越早出錯代價越小原則
消除強制類型轉換數組
- 泛型的一個附帶好處是,使用時直接獲得目標類型,消除許多強制類型轉換
- 所得即所需,這使得代碼更加可讀,而且減小了出錯機會
潛在的性能收益安全
- 因爲泛型的實現方式,支持泛型(幾乎)不須要 JVM 或類文件更改
- 全部工做都在編譯器中完成
- 編譯器生成的代碼跟不使用泛型(和強制類型轉換)時所寫的代碼幾乎一致,只是更能確保類型安全而已
使用方式
- 泛型的本質是參數化類型,也就是說所操做的數據類型被指定爲一個參數。
- 類型參數的意義是告訴編譯器這個集合中要存放實例的類型,從而在添加其餘類型時作出提示,在編譯時就爲類型安全作了保證。
- 參數類型能夠用在類、接口和方法的建立中,分別稱爲泛型類、泛型接口、泛型方法。
通配符
- 有時候但願傳入的類型有一個指定的範圍,從而能夠進行一些特定的操做,這時候就是通配符邊界登場的時候了。
泛型中有三種通配符形式:框架
- <?> 無限制通配符
- <? extends E> extends關鍵字聲明瞭類型的上界,表示參數化的類型多是所指定的類型,或者是此類型的子類
- <? super E> super關鍵字聲明瞭類型的下界,表示參數化的類型多是指定的類型,或者是此類型的父類
無限制通配符 <?>
- 要使用泛型,可是不肯定或者不關心實際要操做的類型,可使用無限制通配符(尖括號裏一個問號,即 <?> ),表示能夠持有任何類型。
- 大部分狀況下,這種限制是好的,但這使得一些理應正確的基本操做都沒法完成。
private void swap(List<?> list, int i, int j){
Object o = list.get(i);
list.set(j,o);
}
- 這個代碼看上去應該是正確的,但 Java 編譯器會提示編譯錯誤,set 語句是非法的。編譯器提示咱們把方法中的 List<?> 改爲List<Object> 。
? 和 Object 不同嗎?編輯器
- List<?> 表示未知類型的列表,而 List<Object> 表示任意類型的列表。好比傳入 List<String> ,這時 List 的元素類型就是 String,想要往 List 裏添加一個 Object,這固然是不能夠的。
上界通配符 < ? extends E>
在類型參數中使用 extends 表示這個泛型中的參數必須是 E 或者 E 的子類,這樣有兩個好處:性能
- 若是傳入的類型不是 E 或者 E 的子類,編譯不成功
- 泛型中調用使用 E 的方法,要否則必須強轉成 E 才能調用
下界通配符 < ? super E>
在類型參數中使用 super 表示這個泛型中的參數必須是 E 或者 E 的父類。翻譯
private <E> void add(List<? super E> dst, List<E> src){
for (E e : src) {
dst.add(e);
}
}
- dst 類型 「大於等於」 src 的類型,這裏的「大於等於」是指 dst 表示的範圍比 src 要大,所以裝得下 dst 的容器也就能裝 src。向上轉型老是合理的。
通配符比較
- 無限制通配符 <?> 和 Object 有些類似,用於表示無限制或者不肯定範圍的場景。<?> 至關於 <? extends Object> 。
- < ? extends E> 用於靈活讀取,使得方法能夠讀取 E 或 E 的任意子類型的容器對象。
- < ? super E> 用於靈活寫入或比較,使得對象能夠寫入父類型的容器,使得父類型的比較方法能夠應用於子類對象。
- 爲了得到最大限度的靈活性,要在表示 生產者或者消費者 的輸入參數上使用通配符,使用的規則就是:生產者有上限、消費者有下限:PECS: producer-extends, costumer-super。
- T 的生產者的意思就是結果會返回 T (get),這就要求返回一個具體的類型,必須有上限纔夠具體;
- T 的消費者的意思是要操做 T (set),這就要求操做的容器要夠大,因此容器須要是 T 的父類,即 super T;
private <E extends Comparable<? super E>> E max(List<? extends E> e1){
if (e1 == null){
return null;
}
Iterator<? extends E> iterator = e1.iterator();
E result = iterator.next();
while (iterator.hasNext()){
E next = iterator.next();
if (next.compareTo(result) > 0){
result = next;
}
}
return result;
}
- 要進行比較,因此 E 須要是可比較的類,所以須要 extends Comparable<…>
- Comparable<? super E> 要對 E 進行比較,即 E 的消費者,因此須要用 super
- 而參數 List<? extends E> 表示要操做的數據是 E 的子類的列表。
類型擦除
Java 中的泛型和 C++ 中的模板有一個很大的不一樣:code
- C++ 中模板的實例化會爲每一種類型都產生一套不一樣的代碼,這就是所謂的代碼膨脹。
- Java 中並不會產生這個問題。虛擬機中並無泛型類型對象,全部的對象都是普通類。
- 在 Java 中,泛型是 Java 編譯器的概念,用泛型編寫的 Java 程序和普通的 Java程序基本相同,只是多了一些參數化的類型同時少了一些類型轉換。
- 實際上泛型程序也是首先被轉化成通常的、不帶泛型的 Java 程序後再進行處理的,編譯器自動完成了從 Generic Java 到普通Java 的翻譯,Java 虛擬機運行時對泛型基本一無所知。
- 當編譯器對帶有泛型的java代碼進行編譯時,它會去執行類型檢查和類型推斷,而後生成普通的不帶泛型的字節碼,這種普通的字節碼能夠被通常的 Java 虛擬機接收並執行,這在就叫作類型擦除(type erasure)。
- 實際上不管你是否使用泛型,集合框架中存放對象的數據類型都是 Object。
List<String> strings = new ArrayList<>();
List<Integer> integers = new ArrayList<>();
System.out.println(strings.getClass() == integers.getClass());//true
上面代碼輸出結果並非預期的 false,而是 true。其緣由就是泛型的擦除。對象
實現原理
- Java 編輯器會將泛型代碼中的類型徹底擦除,使其變成原始類型。
- 接着 Java編譯器會在這些代碼中加入類型轉換,將原始類型轉換成想要的類型。這些操做都是編譯器後臺進行,能夠保證類型安全。
- 泛型就是一個語法糖,它運行時沒有存儲任何類型信息。
擦除致使的泛型不可變性
- 協變:若是 A 是 B 的父類,而且 A 的容器(好比 List< A>)也是 B 的容(List< B > ) 的父類,則稱之爲協變的(父子關係保持一致)
- 逆變:若是 A 是 B 的父類,可是 A 的容器 是 B 的容器的子類,則稱之爲逆變(放入容器就篡位了)
- 不可變:不論 A B 有什麼關係,A 的容器和 B 的容器都沒有父子關係,稱之爲不可變。
- Java 中數組是協變的,泛型是不可變的。
若是想要讓某個泛型類具備協變性,就須要用到邊界。接口
總結
- 泛型是經過擦除來實現的。所以泛型只在編譯時強化它的類型信息,而在運行時丟棄(或者擦除)它的元素類型信息。擦除使得使用泛型的代碼能夠和沒有使用泛型的代碼隨意互用。
- 數組中不能使用泛型
Java 中 List<Object> 和原始類型 List 之間的區別?
- 在編譯時編譯器不會對原始類型進行類型安全檢查,卻會對帶參數的類型進行檢查
- 經過使用 Object 做爲類型,能夠告知編譯器該方法能夠接受任何類型的對象,好比String 或 Integer
- 能夠把任何帶參數的類型傳遞給原始類型 List,但卻不能把 List< String> 傳遞給接受 List< Object>的方法,由於泛型的不可變性,會產生編譯錯誤。
區分原始類型和泛型變量的類型
- 在調用泛型方法的時候,能夠指定泛型,也能夠不指定泛型。
- 在不指定泛型的狀況下,泛型變量的類型爲該方法中的幾種類型的同一個父類的最小級,直到Object。
- 在指定泛型的時候,該方法中的幾種類型必須是該泛型實例類型或者其子類。
public class Test2{
public static void main(String[] args) {
/**不指定泛型的時候*/
int i=Test2.add(1, 2); //這兩個參數都是Integer,因此T爲Integer類型
Number f=Test2.add(1, 1.2);//這兩個參數一個是Integer,以風格是Float,因此取同一父類的最小級,爲Number
Object o=Test2.add(1, "asd");//這兩個參數一個是Integer,以風格是Float,因此取同一父類的最小級,爲Object
/**指定泛型的時候*/
int a=Test2.<Integer>add(1, 2);//指定了Integer,因此只能爲Integer類型或者其子類
int b=Test2.<Integer>add(1, 2.2);//編譯錯誤,指定了Integer,不能爲Float
Number c=Test2.<Number>add(1, 2.2); //指定爲Number,因此能夠爲Integer和Float
}
//這是一個簡單的泛型方法
public static <T> T add(T x,T y){
return y;
}
}
- 類型檢查就是針對引用的,誰是一個引用,用這個引用調用泛型方法,就會對這個引用調用的方法進行類型檢測,而無關它真正引用的對象。