探秘 Java 泛型:從類型參數(shù)到邊界限制與類型擦除
在 Java 編程中,大家或許都遭遇過令人頭疼的ClassCastException,尤其是在處理如Integer、String等不同類型對(duì)象時(shí)。這個(gè)異常通常是由于將對(duì)象強(qiáng)制轉(zhuǎn)換為錯(cuò)誤的數(shù)據(jù)類型所導(dǎo)致的。不過,Java 中的泛型可以幫助我們解決這一問題。
為什么我們需要泛型?
讓我們從一個(gè)簡(jiǎn)單的例子開始。我們首先將不同類型的對(duì)象添加到一個(gè)ArrayList中。然后打印它們的值。
List list = new ArrayList();
list.add("Hello");
String str = (String) list.get(0);
System.out.println("String: " + str);
這里,我們向ArrayList添加了一個(gè)String對(duì)象。由于代碼是自己編寫,我們清楚元素類型,但編譯器并不知曉。所以從列表獲取值時(shí)得到的是Object類型,必須進(jìn)行顯式強(qiáng)制轉(zhuǎn)換。
list.add(123);
String number = (String) list.get(1);
System.out.println("Number: " + number);
如果我們向這個(gè)列表中添加一個(gè)Integer并嘗試獲取該值,我們將得到一個(gè)ClassCastException,因?yàn)镮nteger對(duì)象不能被強(qiáng)制轉(zhuǎn)換為String。 而使用泛型,就能解決上述兩個(gè)問題。使用菱形運(yùn)算符明確指定列表中保存的對(duì)象類型,可實(shí)現(xiàn)編譯時(shí)檢查,無需顯式強(qiáng)制轉(zhuǎn)換。
List<String> list = new ArrayList<>();
list.add("Hello");
String str = list.get(0); // 無需顯式強(qiáng)制轉(zhuǎn)換
System.out.println("String: " + str);
list.add(123); // 拋出編譯時(shí)錯(cuò)誤
類型參數(shù)命名約定
在前面示例中,List<String>的使用限制了列表可保存的對(duì)象類型。再看Box類處理不同類型數(shù)據(jù)的示例:
public class Box<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public static void main(String[] args) {
Box<String> stringBox = new Box<>();
stringBox.setValue("Hello, world!");
System.out.println(stringBox.getValue());
Box<Integer> integerBox = new Box<>();
integerBox.setValue(123);
System.out.println(integerBox.getValue());
}
}
注意Box<T>類的聲明,這里T是類型參數(shù),表示Box類可處理該類型的任意對(duì)象。在main方法中創(chuàng)建Box<String>和Box<Integer>實(shí)例,確保了類型安全。
根據(jù)官方文檔,類型參數(shù)名稱通常為單個(gè)大寫字母。常見的類型參數(shù)名稱有:
- E - 元素(廣泛用于 Java 集合框架)
- K - 鍵
- N - 數(shù)字
- T - 類型
- V - 值
- S、U、V等 - 第二、第三、第四種類型
讓我們看看如何編寫一個(gè)泛型方法:
public static <T> void printArray(T[] inputArr) {
for (T element : inputArr) {
System.out.print(element + " ");
}
System.out.println();
}
這里,我們接受任何類型的數(shù)組并打印其元素。請(qǐng)注意,你需要在方法返回類型之前的尖括號(hào)<>中指定泛型類型參數(shù)T。方法體遍歷我們作為參數(shù)傳遞的任何類型T的數(shù)組,并打印每個(gè)元素。
public static void main(String[] args) {
// 創(chuàng)建不同類型的數(shù)組(Integer、Double和Character)
Integer[] intArr = {1, 2, 3, 4, 5};
Double[] doubleArr = {1.1, 2.2, 3.3, 4.4, 5.5};
Character[] charArr = {'H', 'E', 'L', 'L', 'O'};
System.out.println("Integer數(shù)組包含:");
printArray(intArr); // 傳遞一個(gè)Integer數(shù)組
System.out.println("Double數(shù)組包含:");
printArray(doubleArr); // 傳遞一個(gè)Double數(shù)組
System.out.println("Character數(shù)組包含:");
printArray(charArr); // 傳遞一個(gè)Character數(shù)組
}
我們可以通過傳遞不同類型的數(shù)組(Integer、Double、Character)來調(diào)用這個(gè)泛型方法,你會(huì)看到你的程序?qū)⒋蛴〕鲞@些數(shù)組的每個(gè)元素。
泛型的限制
在泛型中,我們使用邊界來限制泛型類、接口或方法可以接受的類型。有兩種類型:
1. 上界
這用于將泛型類型限制為上限。要定義上界,你使用extends關(guān)鍵字。通過指定上界,你確保類、接口或方法接受指定的類型及其所有子類。 語法如下:<T extends SuperClass>。例如,修改Box類:
class Box<T extends Number> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
在這個(gè)例子中,T可以是任何擴(kuò)展Number的類型,如Integer、Double或Float。
2. 下界
這用于將泛型類型限制為下限。要定義下界,你使用super關(guān)鍵字。通過指定下界,你確保類、接口或方法接受指定的類型及其所有超類。 語法如下:<T super SubClass>。以下是使用下界的示例:
public static void printList(List<? super Integer> list) {
for (Object element : list) {
System.out.print(element + " ");
}
System.out.println();
}
下界<? super Integer>的使用確保你可以將指定的類型及其所有超類(在這種情況下是Integer、Number或Object的列表)傳遞給printList方法。
什么是通配符?
你在上一個(gè)示例中看到的?被稱為通配符。你可以使用它們來引用未知類型。你可以使用帶有上界的通配符,在這種情況下它看起來像這樣:<? extends Number>。它也可以與下界一起使用,如<? super Integer>。
類型擦除
我們?cè)陬悺⒔涌诨蚍椒ㄖ惺褂玫姆盒皖愋蛢H在編譯時(shí)可用,并且在運(yùn)行時(shí)會(huì)被刪除。這樣做是為了確保向后兼容性,因?yàn)榕f版本的Java(Java 1.5之前)不支持它。 編譯器利用泛型類型信息確保類型安全。類型擦除過程如下:
- 對(duì)于有界泛型類型,編譯器會(huì)將其擦除為它的上界類型。例如,class Box<T extends Number>,T會(huì)被擦除為Number。
- 對(duì)于無界泛型類型(如class Box<T>),T會(huì)被擦除為Object。所以在運(yùn)行時(shí),實(shí)際上并不能獲取到泛型參數(shù)的具體類型信息。
import java.util.ArrayList;
import java.util.List;
class GenericExample<T> {
private List<T> list = new ArrayList<>();
public void add(T element) {
list.add(element);
}
public T get(int index) {
return list.get(index);
}
}
當(dāng)編譯器編譯這段代碼時(shí),T會(huì)被擦除。對(duì)于add方法,實(shí)際上變成了類似public void add(Object element)(如果T是無界的)。對(duì)于get方法,返回值類型也被擦除為Object,不過編譯器會(huì)在需要的時(shí)候插入強(qiáng)制類型轉(zhuǎn)換。
結(jié)論
本文深入探討了 Java 中的泛型概念及其使用方法,并給出了多個(gè)基本示例。理解和運(yùn)用泛型能增強(qiáng)程序類型安全性,消除顯式強(qiáng)制轉(zhuǎn)換需求,使代碼更具重用性和可維護(hù)性。希望通過本文的介紹,大家能在 Java 編程中更好地運(yùn)用泛型,提升代碼質(zhì)量。