java泛型萬用字元詳解
-
前言
-
泛型帶來的好處
-
泛型中萬用字元
-
常用的 T,E,K,V,?
-
?無界萬用字元
-
上界萬用字元 < ? extends E>
-
下界萬用字元 < ? super E>
-
?和 T 的區別
-
`Class`和 `Class`區別
-
小結
前言
Java 泛型(generics)是 JDK 5 中引入的一個新特性, 泛型提供了編譯時型別安全檢測機制,該機制允許開發者在編譯時檢測到非法的型別。
泛型的本質是引數化型別,也就是說所操作的資料型別被指定為一個引數。
泛型帶來的好處
在沒有泛型的情況的下,通過對型別 Object 的引用來實現引數的“任意化”,“任意化”帶來的缺點是要做顯式的強制型別轉換,而這種轉換是要求開發者對實際引數型別可以預知的情況下進行的。對於強制型別轉換錯誤的情況,編譯器可能不提示錯誤,在執行的時候才出現異常,這是本身就是一個安全隱患。
那麼泛型的好處就是在編譯的時候能夠檢查型別安全,並且所有的強制轉換都是自動和隱式的。
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 方法中 省去了強制轉換,可以在編譯時候檢查型別安全,可以用在類,方法,介面上。
泛型中萬用字元
我們在定義泛型類,泛型方法,泛型介面的時候經常會碰見很多不同的萬用字元,比如 T,E,K,V 等等,這些萬用字元又都是什麼意思呢?
常用的 T,E,K,V,?
本質上這些個都是萬用字元,沒啥區別,只不過是編碼時的一種約定俗成的東西。比如上述程式碼中的 T ,我們可以換成 A-Z 之間的任何一個 字母都可以,並不會影響程式的正常執行,但是如果換成其他的字母代替 T ,在可讀性上可能會弱一些。通常情況下,T,E,K,V,?是這樣約定的:
-
?表示不確定的 java 型別
-
T (type) 表示具體的一個java型別
-
K V (key value) 分別代表java鍵值中的Key Value
-
E (element) 代表Element
? 無界萬用字元
先從一個小例子看起,原文在 這裡 。
我有一個父類 Animal 和幾個子類,如狗、貓等,現在我需要一個動物的列表,我的第一個想法是像這樣的:
List<Animal> listAnimals
但是老闆的想法確實這樣的:
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 E>
上屆:用 extends 關鍵字宣告,表示引數化的型別可能是所指定的型別,或者是此型別的子類。
在型別引數中使用 extends 表示這個泛型中的引數必須是 E 或者 E 的子類,這樣有兩個好處:
-
如果傳入的型別不是 E 或者 E 的子類,編譯不成功
-
泛型中可以使用 E 的方法,要不然還得強轉成 E 才能使用
private <K extends A, E extends B> E test(K arg1, E arg2){
E result = arg2;
arg2.compareTo(arg1);
//.....
return result;
}
型別引數列表中如果有多個型別引數上限,用逗號分開
下界萬用字元 < ? super E>
下界: 用 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 t = operate();
// 不可以
?car = operate();
簡單總結下:
T 是一個 確定的 型別,通常用於泛型類和泛型方法的定義,?是一個 不確定 的型別,通常用於泛型方法的呼叫程式碼和形參,不能用於定義類和泛型方法。
區別1:通過 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 而言,就還是需要進行型別轉換。
區別2:型別引數可以多重限定而萬用字元不行
使用 & 符號設定多重邊界(Multi Bounds),指定泛型型別 T 必須是 MultiLimitInterfaceA 和 MultiLimitInterfaceB 的共有子型別,此時變數 t 就具有了所有限定的方法和屬性。對於萬用字元來說,因為它不是一個確定的型別,所以不能進行多重限定。
區別3:萬用字元可以使用超類限定而型別引數不行
型別引數 T 只具有 一種 型別限定方式:
T extends A
但是萬用字元 ? 可以進行 兩種限定:
? extends A
? super A
`Class`和 `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;
來源:https://www.cnblogs.com/minikobe/p/11547220.html
-
前言
-
泛型帶來的好處
-
泛型中萬用字元
-
常用的 T,E,K,V,?
-
?無界萬用字元
-
上界萬用字元 < ? extends E>
-
下界萬用字元 < ? super E>
-
?和 T 的區別
-
`Class`和 `Class`區別
-
小結
前言
Java 泛型(generics)是 JDK 5 中引入的一個新特性, 泛型提供了編譯時型別安全檢測機制,該機制允許開發者在編譯時檢測到非法的型別。
泛型的本質是引數化型別,也就是說所操作的資料型別被指定為一個引數。
泛型帶來的好處
在沒有泛型的情況的下,通過對型別 Object 的引用來實現引數的“任意化”,“任意化”帶來的缺點是要做顯式的強制型別轉換,而這種轉換是要求開發者對實際引數型別可以預知的情況下進行的。對於強制型別轉換錯誤的情況,編譯器可能不提示錯誤,在執行的時候才出現異常,這是本身就是一個安全隱患。
那麼泛型的好處就是在編譯的時候能夠檢查型別安全,並且所有的強制轉換都是自動和隱式的。
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 方法中 省去了強制轉換,可以在編譯時候檢查型別安全,可以用在類,方法,介面上。
泛型中萬用字元
我們在定義泛型類,泛型方法,泛型介面的時候經常會碰見很多不同的萬用字元,比如 T,E,K,V 等等,這些萬用字元又都是什麼意思呢?
常用的 T,E,K,V,?
本質上這些個都是萬用字元,沒啥區別,只不過是編碼時的一種約定俗成的東西。比如上述程式碼中的 T ,我們可以換成 A-Z 之間的任何一個 字母都可以,並不會影響程式的正常執行,但是如果換成其他的字母代替 T ,在可讀性上可能會弱一些。通常情況下,T,E,K,V,?是這樣約定的:
-
?表示不確定的 java 型別
-
T (type) 表示具體的一個java型別
-
K V (key value) 分別代表java鍵值中的Key Value
-
E (element) 代表Element
? 無界萬用字元
先從一個小例子看起,原文在 這裡 。
我有一個父類 Animal 和幾個子類,如狗、貓等,現在我需要一個動物的列表,我的第一個想法是像這樣的:
List<Animal> listAnimals
但是老闆的想法確實這樣的:
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 E>
上屆:用 extends 關鍵字宣告,表示引數化的型別可能是所指定的型別,或者是此型別的子類。
在型別引數中使用 extends 表示這個泛型中的引數必須是 E 或者 E 的子類,這樣有兩個好處:
-
如果傳入的型別不是 E 或者 E 的子類,編譯不成功
-
泛型中可以使用 E 的方法,要不然還得強轉成 E 才能使用
private <K extends A, E extends B> E test(K arg1, E arg2){
E result = arg2;
arg2.compareTo(arg1);
//.....
return result;
}
型別引數列表中如果有多個型別引數上限,用逗號分開
下界萬用字元 < ? super E>
下界: 用 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 t = operate();
// 不可以
?car = operate();
簡單總結下:
T 是一個 確定的 型別,通常用於泛型類和泛型方法的定義,?是一個 不確定 的型別,通常用於泛型方法的呼叫程式碼和形參,不能用於定義類和泛型方法。
區別1:通過 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 而言,就還是需要進行型別轉換。
區別2:型別引數可以多重限定而萬用字元不行
使用 & 符號設定多重邊界(Multi Bounds),指定泛型型別 T 必須是 MultiLimitInterfaceA 和 MultiLimitInterfaceB 的共有子型別,此時變數 t 就具有了所有限定的方法和屬性。對於萬用字元來說,因為它不是一個確定的型別,所以不能進行多重限定。
區別3:萬用字元可以使用超類限定而型別引數不行
型別引數 T 只具有 一種 型別限定方式:
T extends A
但是萬用字元 ? 可以進行 兩種限定:
? extends A
? super A
`Class`和 `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;