Java中的泛型使用詳解
泛型類
語法
class 泛型類名稱<類型形參列表> {
// 這里可以使用類型參數(shù)
}
class ClassName<T1, T2, ..., Tn> {
}
class 泛型類名稱<類型形參列表> extends 繼承類/* 這里可以使用類型參數(shù) */ {
// 這里可以使用類型參數(shù)
}
class ClassName<T1, T2, ..., Tn> extends ParentClass<T1> {
// 可以只使用部分類型參數(shù)
}使用
泛型類<類型實參> 變量名; // 定義一個泛型類引用
new 泛型類<類型實參>(構(gòu)造方法實參); // 實例化一個泛型類對象
class MyArray<T> {
public Object[] array = new Object[10];
public void set(int pos,T val) {
array[pos] = val;
}
public T get(int pos) {
return (T)array[pos];
}
public Object[] getArray() {
return array;
}
}
public class Test {
public static void main(String[] args) {
MyArray<String> myArray = new MyArray<>();
}
}泛型上界
在定義泛型類時,有時需要對傳入的類型變量做一定的約束,可以通過類型邊界來約束。
定義
class 泛型類名稱<類型形參 extends 類型邊界> {
...
} 使用
class MyArray<T extends Number> {
public Object[] array = new Object[10];
public void set(int pos,T val) {
array[pos] = val;
}
public T get(int pos) {
return (T)array[pos];
}
public Object[] getArray() {
return array;
}
}
public class Test {
public static void main(String[] args) {
// 編譯錯誤,因為 String 不是 Number 的子類型
// MyArray<String> myArray1 = new MyArray<>();
// 正常,因為 Integer 是 Number 的子類型
MyArray<Integer> myArray = new MyArray<>();
}
}class Alg<T extends Comparable<T>> {
public T findMax(T[] array) {
T max = array[0];
for (int i = 1; i < array.length; i++) {
if(array[i].compareTo(max) > 0) {
max = array[i];
}
}
return max;
}
}類型擦除
Java中的泛型擦除是Java泛型實現(xiàn)的一個關(guān)鍵特性,它允許Java在編譯時檢查類型安全,但在運行時忽略泛型信息。這意味著,盡管你可以在編譯時指定泛型類型,但在運行時,JVM看到的只是原始類型,并不保留任何關(guān)于泛型參數(shù)的信息。
class MyArray<E> {
// E 會被擦除為 Object
}
class MyArray<E extends Comparable<E>> {
// E 被擦除為 Comprable
}示例
class Alg<T extends Comparable<T>> {
public T findMax(T[] array) {
T max = array[0];
for (int i = 1; i < array.length; i++) {
if(array[i].compareTo(max) > 0) {
max = array[i];
}
}
return max;
}
}針對上面的代碼,編譯結(jié)果如下:

編譯器在類型擦除階段做什么?
一、擦除泛型類型信息
移除類型參數(shù):編譯器會移除所有與泛型相關(guān)的類型參數(shù)信息。這意味著在編譯后的字節(jié)碼中,將不再包含任何泛型類型參數(shù)的信息。
替換為原始類型:泛型類型參數(shù)會被替換為其上界(如果指定了上界的話,比如T extends Comparable<T>中的T會被替換為Comparable<T>;如果沒有指定上界,則默認(rèn)為Object)。例如,List<String>和List<Integer>在編譯后的字節(jié)碼中都表現(xiàn)為List。
二、調(diào)整類型信息
方法參數(shù)、局部變量和字段的類型調(diào)整:除了泛型類型本身被擦除外,編譯器還會相應(yīng)地調(diào)整方法參數(shù)、局部變量和字段的類型,以確保它們在運行時的一致性。
三、插入類型轉(zhuǎn)換
類型強(qiáng)制轉(zhuǎn)換:由于類型信息在運行時不可用,編譯器會在需要的地方插入必要的類型轉(zhuǎn)換。例如,從List<String>中獲取元素時,雖然運行時這個列表只是普通的List,但編譯器會在幕后添加一個從Object到String的顯式類型轉(zhuǎn)換。
四、確保類型安全
編譯時類型檢查:在編譯階段,編譯器會利用泛型類型信息進(jìn)行類型檢查,確保類型安全。例如,編譯器會阻止向List<String>中添加整數(shù)或其他非字符串對象,或者嘗試將List<Integer>賦值給List<Double>等不兼容的操作。
五、橋接方法的生成
向后兼容性:為了保持與未使用泛型的舊代碼的兼容性,編譯器可能會為泛型類生成橋接方法。這些橋接方法允許泛型類的方法與原生類型的方法共存,并且可以相互調(diào)用。
總的來說,編譯器在類型擦除階段的主要目標(biāo)是確保泛型代碼在運行時能夠安全、正確地執(zhí)行,同時盡量減少對現(xiàn)有代碼的影響。通過移除泛型類型信息并插入必要的類型轉(zhuǎn)換,編譯器使得泛型代碼能夠與非泛型代碼無縫對接,從而保證了Java平臺的整體一致性和穩(wěn)定性。
通配符
?用于在泛型的使用,即為通配符。
class Message<T> {
private T message;
public T getMessage() {
return message;
}
public void setMessage(T message) {
this.message = message;
}
}
public class Test{
//可以傳入任意類型的Message
public static void fun(Message<?> temp){
System.out.println(temp.getMessage());
}
public static void main(String[] args) {
Message<String> message = new Message<>();
message.setMessage("你好!");
fun(message);
Message<Integer> message2 = new Message<>();
message2.setMessage(199);
fun(message2);
}
}通配符上界
<? extends 上界>
上界通配符用于指定一個類型的上界,即泛型參數(shù)必須是該類型或其子類。這種通配符主要用于讀取操作,因為它允許你安全地讀取數(shù)據(jù),但不允許你寫入數(shù)據(jù)(除了null,因為null可以被視為任何類型的實例)。
特點:
add()方法:不能添加任何元素(除了null),因為編譯器無法確定具體的類型,添加其他元素可能會導(dǎo)致類型安全問題。
get()方法:可以安全地讀取元素,并且返回的類型是T或T的子類,但通常需要用T或T的父類(如Object)來接收,因為編譯器無法確定具體的子類類型。
class Food {
}
class Fruit extends Food {
}
class Apple extends Fruit {
}
class Banana extends Fruit {
}
class Message<T> {
private T message;
public T getMessage() {
return message;
}
public void setMessage(T message) {
this.message = message;
}
}
public class Test1 {
public static void funExtends(Message<? extends Fruit> temp){
System.out.println(temp.getMessage());
//通配符的上界來說 是不可以進(jìn)行修改元素的
/* temp.setMessage(new Banana());
temp.setMessage(new Apple());*/
//向上轉(zhuǎn)型
Fruit fruit = temp.getMessage();
}
public static void main(String[] args) {
Message<Apple> message1 = new Message<>();
message1.setMessage(new Apple());
Message<Banana> message2 = new Message<>();
message2.setMessage(new Banana());
funExtends(message1);
funExtends(message2);
}
}通配符下界
<? super 下界>
下界通配符用于指定一個類型的下界,即泛型參數(shù)必須是該類型或其父類。這種通配符主要用于寫入操作,因為它允許你安全地添加T或T的子類類型的元素,但在讀取時只能確保是Object類型。
特點:
add()方法:可以添加T或T的子類類型的元素,因為所有這些類型都是下界類型的子類或相同類型。
get()方法:讀取元素時,由于類型的不確定性,通常只能用Object類型來接收。如果需要具體的類型,則需要進(jìn)行類型轉(zhuǎn)換。
class Food {
}
class Fruit extends Food {
}
class Apple extends Fruit {
}
class Banana extends Fruit {
}
class Plate<T> {
private T plate ;
public T getPlate() {
return plate;
}
public void setPlate(T plate) {
this.plate = plate;
}
}
public class Test1 {
public static void funSuper(Plate<? super Fruit> temp){
// 此時可以修改??!添加的是Fruit 或者Fruit的子類
temp.setPlate(new Apple());//這個是Fruit的子類 向上轉(zhuǎn)型
temp.setPlate(new Banana());//這個是Fruit的本身
// Fruit fruit = temp.getPlate(); //不能接收,這里無法確定是哪個父類
System.out.println(temp.getPlate());//只能直接輸出
}
public static void main(String[] args) {
Plate<Fruit> message1 = new Plate<>();
Plate<Food> message2 = new Plate<>();
funSuper(message1);
funSuper(message2);
}
}泛型方法
語法
方法限定符 <類型形參列表> 返回值類型 方法名稱(形參列表) { ... }
使用
class Alg2 {
//泛型方法
public static <T extends Comparable<T>> T findMax(T[] array) {
T max = array[0];
for (int i = 1; i < array.length; i++) {
if(array[i].compareTo(max) > 0) {
max = array[i];
}
}
return max;
}
}
public class Test2 {
public static void main1(String[] args) {
Integer[] array = {1,13,51,71,19};
Integer ret = Alg2.<Integer>findMax(array);
System.out.println(ret);
}
}泛型的限制
1. 不能使用基本數(shù)據(jù)類型作為泛型類型參數(shù)
Java中的泛型只能使用引用數(shù)據(jù)類型作為類型參數(shù),不能使用基本數(shù)據(jù)類型(如int、double等)。如果需要使用基本數(shù)據(jù)類型的泛型,通常需要使用它們對應(yīng)的包裝類(如Integer、Double等)。2. 不能創(chuàng)建泛型類型的數(shù)組
由于類型擦除的原因,Java不允許直接創(chuàng)建泛型類型的數(shù)組。例如,List<String>[] listArray = new ArrayList<String>[10]; 是不允許的。如果需要類似的功能,可以考慮使用ArrayList<List<String>>等集合類來實現(xiàn)。3. 不能實例化泛型類型的對象(除了使用new操作符的匿名內(nèi)部類)
Java不允許直接實例化泛型類型的對象,如List<String> list = new List<String>(); 是不允許的。應(yīng)該使用具體的類來實例化泛型,如List<String> list = new ArrayList<>();。4. 運行時類型查詢的限制
由于類型擦除,在運行時無法直接查詢泛型參數(shù)的具體類型。例如,instanceof 運算符和getClass()方法不能用于檢查泛型類型參數(shù)的具體類型。這意味著,在運行時,所有的泛型類型都會被當(dāng)作它們的原始類型(即擦除后的類型)來處理。5. 泛型類型參數(shù)不具有繼承關(guān)系
即使兩個類之間存在繼承關(guān)系,它們的泛型類型參數(shù)之間也不具有繼承關(guān)系。例如,List<A>并不是List<B>的子類,即使A是B的子類。這要求開發(fā)者在編寫泛型代碼時特別注意類型安全。6. 泛型上界和下界的限制
上界:通過extends關(guān)鍵字,可以限制泛型類型參數(shù)必須是某個類型的子類或?qū)崿F(xiàn)類。這有助于在編譯時檢查類型兼容性,提高代碼的安全性。
下界:通過super關(guān)鍵字,可以限制泛型類型參數(shù)必須是某個類型的父類型。這允許在泛型方法或類中靈活處理不同但兼容的類型。
7. 泛型通配符的限制
泛型通配符(如?、<? extends T>、<? super T>)用于表示未知類型或受限類型。然而,它們也帶來了一些限制,如在使用上界通配符時不能添加除null以外的元素,而在使用下界通配符時讀取的元素類型只能為Object等。8. 反射和泛型
Java的反射機(jī)制可以在運行時訪問類的信息,但由于類型擦除,反射無法直接獲取泛型類型參數(shù)的具體信息。這要求開發(fā)者在使用反射和泛型時需要特別注意類型安全和類型轉(zhuǎn)換的問題。
到此這篇關(guān)于Java中的泛型使用詳解的文章就介紹到這了,更多相關(guān)java泛型內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot之bootstrap和application的區(qū)別解讀
這篇文章主要介紹了SpringBoot之bootstrap和application的區(qū)別及說明,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-03-03
java中PreparedStatement和Statement詳細(xì)講解
這篇文章主要介紹了java中PreparedStatement和Statement詳細(xì)講解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-11-11
mybatis-plus通用枚舉@JsonValue接收參數(shù)報錯No enum constant
最近在使用mybatis-plus時用到了通用枚舉,遇到了問題,本文主要介紹了mybatis-plus通用枚舉@JsonValue接收參數(shù)報錯No enum constant,具有一定的參考價值,感興趣的可以了解一下2023-09-09
java利用pdfbox+poi往pdf插入數(shù)據(jù)
這篇文章主要給大家介紹了關(guān)于java利用pdfbox+poi如何往pdf插入數(shù)據(jù)的相關(guān)資料,文中通過實例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2022-02-02
Java?IO及BufferedReader.readline()出現(xiàn)的Bug
這篇文章主要介紹了Java?IO及BufferedReader.readline()出現(xiàn)的Bug,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12
Java解除文件占用即Dom4j操作后實現(xiàn)xml關(guān)流
這篇文章主要介紹了Java解除文件占用即Dom4j操作后實現(xiàn)xml關(guān)流,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-04-04
JavaWeb動態(tài)導(dǎo)出Excel可彈出下載
這篇文章主要介紹了JavaWeb動態(tài)導(dǎo)出Excel,對Excel可彈出進(jìn)行下載操作,感興趣的小伙伴們可以參考一下2016-03-03

