Java 泛型(generics)是 JDK 5 中引入的一個新特性, 泛型提供了編譯時類型安全檢測機制,該機制容許開發者在編譯時檢測到非法的類型。html
泛型的本質是參數化類型,也就是說所操做的數據類型被指定爲一個參數。java
在沒有泛型的狀況的下,經過對類型 Object 的引用來實現參數的「任意化」,「任意化」帶來的缺點是要作顯式的強制類型轉換,而這種轉換是要求開發者對實際參數類型能夠預知的狀況下進行的。對於強制類型轉換錯誤的狀況,編譯器可能不提示錯誤,在運行的時候纔出現異常,這是自己就是一個安全隱患。web
那麼泛型的好處就是在編譯的時候可以檢查類型安全,而且全部的強制轉換都是自動和隱式的。安全
public class GlmapperGeneric<T> { private T t; public void set(T t) { this.t = t; } public T get() { return t; } public static void main(String[] args) { // do nothing } /** * 不指定類型 */ public void noSpecifyType(){ GlmapperGeneric glmapperGeneric = new GlmapperGeneric(); glmapperGeneric.set("test"); // 須要強制類型轉換 String test = (String) glmapperGeneric.get(); System.out.println(test); } /** * 指定類型 */ public void specifyType(){ GlmapperGeneric<String> glmapperGeneric = new GlmapperGeneric(); glmapperGeneric.set("test"); // 不須要強制類型轉換 String test = glmapperGeneric.get(); System.out.println(test); } } 複製代碼
上面這段代碼中的 specifyType 方法中 省去了強制轉換,能夠在編譯時候檢查類型安全,能夠用在類,方法,接口上。bash
咱們在定義泛型類,泛型方法,泛型接口的時候常常會遇見不少不一樣的通配符,好比 T,E,K,V 等等,這些通配符又都是什麼意思呢?app
本質上這些個都是通配符,沒啥區別,只不過是編碼時的一種約定俗成的東西。好比上述代碼中的 T ,咱們能夠換成 A-Z 之間的任何一個 字母均可以,並不會影響程序的正常運行,可是若是換成其餘的字母代替 T ,在可讀性上可能會弱一些。一般狀況下,T,E,K,V,? 是這樣約定的:post
先從一個小例子看起,原文在 這裏 。this
我有一個父類 Animal 和幾個子類,如狗、貓等,如今我須要一個動物的列表,個人第一個想法是像這樣的:編碼
List<Animal> listAnimals
複製代碼
可是老闆的想法確實這樣的:spa
List<? extends Animal> listAnimals
複製代碼
爲何要使用通配符而不是簡單的泛型呢?通配符其實在聲明局部變量時是沒有什麼意義的,可是當你爲一個方法聲明一個參數時,它是很是重要的。
static int countLegs (List<? extends Animal > animals ) { int retVal = 0; for ( Animal animal : animals ) { retVal += animal.countLegs(); } return retVal; } static int countLegs1 (List< Animal > animals ){ int retVal = 0; for ( Animal animal : animals ) { retVal += animal.countLegs(); } return retVal; } public static void main(String[] args) { List<Dog> dogs = new ArrayList<>(); // 不會報錯 countLegs( dogs ); // 報錯 countLegs1(dogs); } 複製代碼
當調用 countLegs1 時,就會飄紅,提示的錯誤信息以下:
因此,對於不肯定或者不關心實際要操做的類型,可使用無限制通配符(尖括號裏一個問號,即 <?> ),表示能夠持有任何類型。像 countLegs 方法中,限定了上屆,可是不關心具體類型是什麼,因此對於傳入的 Animal 的全部子類均可以支持,而且不會報錯。而 countLegs1 就不行。
上屆:用 extends 關鍵字聲明,表示參數化的類型多是所指定的類型,或者是此類型的子類。
在類型參數中使用 extends 表示這個泛型中的參數必須是 E 或者 E 的子類,這樣有兩個好處:
private <K extends A, E extends B> E test(K arg1, E arg2){ E result = arg2; arg2.compareTo(arg1); //..... return result; } 複製代碼
類型參數列表中若是有多個類型參數上限,用逗號分開
下界: 用 super 進行聲明,表示參數化的類型多是所指定的類型,或者是此類型的父類型,直至 Object
在類型參數中使用 super 表示這個泛型中的參數必須是 E 或者 E 的父類。
private <T> void test(List<? super T> dst, List<T> src){ for (T t : src) { dst.add(t); } } public static void main(String[] args) { List<Dog> dogs = new ArrayList<>(); List<Animal> animals = new ArrayList<>(); new Test3().test(animals,dogs); } // Dog 是 Animal 的子類 class Dog extends Animal { } 複製代碼
dst 類型 「大於等於」 src 的類型,這裏的「大於等於」是指 dst 表示的範圍比 src 要大,所以裝得下 dst 的容器也就能裝 src 。
?和 T 都表示不肯定的類型,區別在於咱們能夠對 T 進行操做,可是對 ? 不行,好比以下這種 :
// 能夠 T t = operate(); // 不能夠 ? car = operate(); 複製代碼
簡單總結下:
T 是一個 肯定的 類型,一般用於泛型類和泛型方法的定義,?是一個 不肯定 的類型,一般用於泛型方法的調用代碼和形參,不能用於定義類和泛型方法。
// 經過 T 來 確保 泛型參數的一致性 public <T extends Number> void test(List<T> dest, List<T> src) //通配符是 不肯定的,因此這個方法不能保證兩個 List 具備相同的元素類型 public void test(List<? extends Number> dest, List<? extends Number> src) 複製代碼
像下面的代碼中,約定的 T 是 Number 的子類才能夠,可是申明時是用的 String ,因此就會飄紅報錯。
不能保證兩個 List 具備相同的元素類型的狀況
GlmapperGeneric<String> glmapperGeneric = new GlmapperGeneric<>();
List<String> dest = new ArrayList<>();
List<Number> src = new ArrayList<>();
glmapperGeneric.testNon(dest,src);
複製代碼
上面的代碼在編譯器並不會報錯,可是當進入到 testNon 方法內部操做時(好比賦值),對於 dest 和 src 而言,就仍是須要進行類型轉換。
使用 & 符號設定多重邊界(Multi Bounds),指定泛型類型 T 必須是 MultiLimitInterfaceA 和 MultiLimitInterfaceB 的共有子類型,此時變量 t 就具備了全部限定的方法和屬性。對於通配符來講,由於它不是一個肯定的類型,因此不能進行多重限定。
類型參數 T 只具備 一種 類型限定方式:
T extends A
複製代碼
可是通配符 ? 能夠進行 兩種限定:
? extends A
? super A 複製代碼
Class<T>
和 Class<?>
區別前面介紹了 ? 和 T 的區別,那麼對於,Class<T>
和 <Class<?>
又有什麼區別呢?Class<T>
和 Class<?>
最多見的是在反射場景下的使用,這裏以用一段發射的代碼來講明下。
// 經過反射的方式生成 multiLimit // 對象,這裏比較明顯的是,咱們須要使用強制類型轉換 MultiLimit multiLimit = (MultiLimit) Class.forName("com.glmapper.bridge.boot.generic.MultiLimit").newInstance(); 複製代碼
對於上述代碼,在運行期,若是反射的類型不是 MultiLimit 類,那麼必定會報 java.lang.ClassCastException 錯誤。
對於這種狀況,則可使用下面的代碼來代替,使得在在編譯期就能直接 檢查到類型的問題:
Class<T>
在實例化的時候,T 要替換成具體類。Class<?>
它是個通配泛型,? 能夠表明任何類型,因此主要用於聲明時的限制狀況。好比,咱們能夠這樣作申明:
// 能夠 public Class<?> clazz; // 不能夠,由於 T 須要指定類型 public Class<T> clazzT; 複製代碼
因此當不知道定聲明什麼類型的 Class 的時候能夠定義一 個Class<?>。
那若是也想 public Class<T> clazzT;
這樣的話,就必須讓當前的類也指定 T ,
public class Test3<T> { public Class<?> clazz; // 不會報錯 public Class<T> clazzT; 複製代碼
本文零碎整理了下 JAVA 泛型中的一些點,不是很全,僅供參考。若是文中有不當的地方,歡迎指正。