泛型是Java的一個重要特性,它使得我們可以在編譯期間發現類型錯誤,避免了在運行期間出現的類型轉換錯誤。除此之外,泛型還可以提高代碼的重用性和可讀性。在這篇文章中,我們將會詳細的講解Java中的泛型,希望能幫助讀者更好的理解泛型的概念和使用。
一、表示器和類型擦除
Java中的泛型實際上是基於類型擦除(type erasure)實現的。在編譯期間,泛型實例會被轉換成無泛型形式,這就是所謂的類型擦除。
例如,下面的代碼演示了泛型的使用:
public class Box<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
}
上述代碼中,Box類使用了一個泛型類型參數T,因此我們可以創建一個Box對象,並指定它的類型參數為String:
Box<String> box = new Box<>();
當我們在編譯期間編譯Box類時,類型擦除會將上述代碼轉換為以下代碼:
public class Box {
private Object t;
public void set(Object t) {
this.t = t;
}
public Object get() {
return t;
}
}
可以看到,泛型類型參數T被轉換成了Object類型,這就是Java中泛型類型的類型擦除機制。
二、通配符和邊界
Java中的通配符(wildcard)與邊界(bounded)可以用來限定泛型類型參數的範圍。通配符有兩種形式:
- ? extends T:表示類型是T的子類或T本身
- ? super T:表示類型是T的超類或T本身
對於上述兩種通配符,我們可以通過以下示例代碼來理解:
public class Container<T> {
private List<T> list = new ArrayList<>();
public void addAll(Collection<? extends T> c) {
list.addAll(c);
}
public void add(T t) {
list.add(t);
}
public void remove(T t) {
list.remove(t);
}
public List<T> getList() {
return list;
}
public static void main(String[] args) {
Container<Number> container = new Container<>();
List<Integer> integers = Arrays.asList(1, 2, 3);
container.addAll(integers);
System.out.println(container.getList()); // [1, 2, 3]
}
}
上述Container類中,addAll方法使用了一個通配符參數c,並限定它是T的子類或T本身(也就是Number的子類或Number本身)。這樣我們就可以使用addAll方法添加Integer類型的元素到Container對象中。如果我們將addAll方法的參數定義為Collection<T>,那麼就不能夠添加Integer類型的元素到Container對象中。
另外,我們還可以使用邊界限定泛型類型參數的範圍,例如:
public class MinMax<T extends Comparable<T>> {
private T min;
private T max;
public MinMax(T[] array) {
if (array.length == 0) {
throw new IllegalArgumentException("array is empty");
}
min = max = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i].compareTo(min) < 0) {
min = array[i];
}
if (array[i].compareTo(max) > 0) {
max = array[i];
}
}
}
public T getMin() {
return min;
}
public T getMax() {
return max;
}
public static void main(String[] args) {
Integer[] integers = new Integer[]{1, 2, 3, 4};
MinMax<Integer> minMax = new MinMax<>(integers);
System.out.println(minMax.getMin()); // 1
System.out.println(minMax.getMax()); // 4
}
}
上述例子中,MinMax類使用了一個泛型類型參數T,並限定它是Comparable<T>的子類或T本身。這樣我們就可以確保MinMax類的實例只會被創建用於該類型具有比較能力的類的實例。
三、類型轉換和擦除
Java的泛型機制在類型擦除的過程中丟失了很多類型信息,這會導致類型轉換與擦除相關的問題。在探討這些問題之前,我們需要先了解虛擬機的類型擦除方式。
在虛擬機中,泛型類型參數T會被轉換為Object類型,同時,在代碼中隱含加入一些類型轉換操作,使得類型轉換過程不會拋出ClassCastException異常。
例如,在Java中,我們可以使用反射實現泛型數組的創建,例如:
public static <T> T[] newArray(Class<T> clazz, int length) {
return (T[]) Array.newInstance(clazz, length);
}
public static void main(String[] args) {
Integer[] integers = newArray(Integer.class, 10);
System.out.println(integers.length); // 10
}
上述newArray方法使用了一個Class類型參數來表示數組的類型,同時在返回值中強制類型轉換為T[]類型。
然而,如果我們使用了通配符或者邊界,就需要注意類型擦除對類型轉換的影響。例如:
public static <T> void copy(List<? extends T> src, List<? super T> dest) {
for (T t : src) {
dest.add(t);
}
}
public static void main(String[] args) {
List<Number> numbers = new ArrayList<>();
List<Integer> integers = Arrays.asList(1, 2, 3);
copy(integers, numbers);
System.out.println(numbers); // [1, 2, 3]
}
上述copy方法使用了兩個通配符參數,在方法內部將src中的元素添加到dest中。在這個過程中,我們使用了被定義為? super T的dest參數,這樣可以確保dest中的元素是T類型的超類。如果我們使用了? extends T的dest參數,那麼就不能正確的執行類型轉換操作,因為我們不能保證dest的元素類型是T的子類。
四、泛型和遺留代碼
在Java中,我們有時需要與一些遺留的代碼進行交互,這些代碼並沒有使用泛型類型參數。在這種情況下,我們可以使用原始類型(raw type)來代替泛型類型,例如:
public class LegacyLibrary {
public void add(List list, Object o) {
list.add(o);
}
}
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
LegacyLibrary legacyLibrary = new LegacyLibrary();
legacyLibrary.add(strings, "hello");
String s = strings.get(0);
System.out.println(s); // hello
}
上述例子中,我們無法修改LegacyLibrary類的源代碼,因此只能使用原始類型來與該類進行交互,這樣就可以將字元串添加到一個泛型List對象中。不過我們需要注意到,這樣做是有潛在風險的,因為我們無法在編譯期間發現類型錯誤,只能在運行期間發現該問題,這可能導致一些Bug的產生。
如果我們使用了SuppressWarnings(“unchecked”)註解,就可以壓制編譯器發出的警告信息。例如:
@SuppressWarnings("unchecked")
public static void add(List list, Object o) {
list.add(o);
}
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
add(strings, "hello");
String s = strings.get(0);
System.out.println(s); // hello
}
上述add方法使用了SuppressWarnings(“unchecked”)註解,這樣就可以避免編譯器發出的”unchecked”警告信息。但是在使用該註解之前,我們需要確保代碼不會導致類型錯誤或者運行期間出現ClassCastException異常。
五、總結
在Java中使用泛型可以提高代碼的可讀性,安全性和重用性,同時可以加速編程的速度。在本文中,我們對Java中的泛型機制進行了詳細的探討,包括通配符和邊界、類型轉換和擦除以及泛型和遺留代碼等內容。
希望讀者能夠通過本文,更好的理解Java中的泛型機制,避免出現類型轉換錯誤和其他一些常見的Bug。
原創文章,作者:小藍,如若轉載,請註明出處:https://www.506064.com/zh-tw/n/248228.html