擦拭法#
編譯器把類型<T>
視為 Object#
public class Pair<T> {
private final T first;
private final T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
public T getLast() {
return last;
}
}
Java 中泛型是在編譯階段的,編譯器將上述代碼經過擦除法,JVM 實際看到的代碼如下所示:
public class Pair {
private final Object first;
private final Object last;
public Pair(Object first, Object last) {
this.first = first;
this.last = last;
}
public Object getFirst() {
return first;
}
public Object getLast() {
return last;
}
}
編譯器根據<T>
實現安全的強制轉型#
Pair<String> p = new Pair<>("Hello", "world");
String first = p.getFirst();
String last = p.getLast();
編譯器根據 T 的類型安全強轉:
Pair p = new Pair("Hello", "world");
String first = (String) p.getFirst();
String last = (String) p.getLast();
擦拭法的局限性#
<T>
不能是基本類型,因為擦除法要將其替換為Object
- 無法獲取攜帶泛型的
Class
,因為泛型是在編譯期,所以獲取到的都為Pair<Object>
例如:
@Test
void testGetClass() {
Pair<String> p1 = new Pair<>("Hello", "World");
Pair<Integer> p2 = new Pair<>(114, 514);
Class c1 = p1.getClass();
Class c2 = p2.getClass();
assertEquals(c1, c2);
assertEquals(c1, Pair.class);
}
- 不恰當的覆寫方法
class Pair<T> {
public boolean equals(T t) {}
}
這是因為,定義的equals(T t)
方法實際上會被擦拭成equals(Object t)
,而這個方法是繼承自Object
的,編譯器會阻止一個實際上會變成覆寫的泛型方法定義。
泛型繼承#
類可以繼承一個泛型類。在繼承了泛型類型的情況下,子類可以獲取父類的泛型類型。例如:IntPair
可以獲取到父類的泛型類型Integer
。
public class IntPair extends Pair<Integer> {
public IntPair(Integer first, Integer last) {
super(first, last);
}
}
class IntPairTest {
@Test
void getSuperParameterizedType() {
Class<IntPair> clazz = IntPair.class;
Type t = clazz.getGenericSuperclass();
if (t instanceof ParameterizedType) {
ParameterizedType pt = (ParameterizedType) t;
Type[] types = pt.getActualTypeArguments(); // may have many types
Type firstType = types[0];
Class<?> typeClass = (Class<?>) firstType;
assertEquals(typeClass, Integer.class);
}
}
}
extends
通配符#
public class PairHelper {
static int add(Pair<Number> p) {
Number first = p.getFirst();
Number last = p.getLast();
return first.intValue() + last.intValue();
}
}
如上,add
函數接收的參數類型為Pair<Number>
,所以我們傳入Pair<Number>
是沒有問題的;但傳入Pair<Integer>
時,會得到一個編譯錯誤,這是因為雖然Integer
是Number
的子類,但是Pair<Integer>
卻不是Pair<Number>
的子類。
為了解決這個問題,可以使用Pair<? extends Number>
,這樣就可以接收泛型類型為Number
子類的Pair
類型了:
static int add(Pair<? extends Number> p) {
Number first = p.getFirst();
Number last = p.getLast();
return first.intValue() + last.intValue();
}
@Test
void addNumberBased() {
assertEquals(579, add(new Pair<Integer>(123, 456)));
}
讀取#
- 能夠從
List<? extends Number>
中獲取到Number
類型,因為其包含的元素是Number
類型或者Number
的自類型。 - 不能從
List<? extends Number>
中獲取到Integer
類型,因為其保存的元素可能是Double
。 - 不能從
List<? extends Number>
中獲取到Double
類型,因為其保存的元素可能是Integer
。
寫入#
- 不能添加
Number
到List<? extends Number>
中,因為其保存的元素可能是Integer
或Double
。 - 不能添加
Integer
到List<? extends Number>
中,因為其保存的元素可能是Double
。 - 不能添加
Number
到List<? extends Number>
中,因為其保存的元素可能是Integer
。
public class ExtendsTest {
List<? extends Number> nums1 = new ArrayList<Number>();
List<? extends Number> nums2 = new ArrayList<Integer>();
List<? extends Number> nums3 = new ArrayList<Double>();
void get() {
Number x = nums1.get(0);
// Integer y = nums2.get(0); // compile error
// Double z = nums3.get(0); // compile error
}
void set() {
/*
nums1.add(new Number() {
@Override
public int intValue() {
return 0;
}
@Override
public long longValue() {
return 0;
}
@Override
public float floatValue() {
return 0;
}
@Override
public double doubleValue() {
return 0;
}
});
*/ // compile error
// nums2.add(new Integer(1)); // compile error
// nums3.add(new Double(1.2)); // compile error
}
}
super
通配符#
<? super T>
描述了通配符下界,即具體的泛型參數需要滿足條件:泛型參數必須是 T
類型或它的父類。
讀取#
- 我們不能保證可以從
List<? super Integer>
類型對象中讀取到Integer
類型的數據,因為其可能是List<Number>
類型的。 - 我們不能保證可以從
List<? super Integer>
類型對象中讀取到Number
類型的數據,因為其可能是List<Object>
類型的。 - 唯一能夠保證的是,我們可以從
List<? super Integer>
類型對象中獲取到一個Object
對象的實例。
寫入#
對於上面的例子中的 List<? super Integer>
array 對象:
- 我們可以添加
Integer
對象到array
中,也可以添加Integer
的子類對象到array
中,因為Integer
及其父類都可以接受Integer
的子類對象。 - 我們不能添加
Double/Number/Object
等不是Integer
的子類的對象到array
中。
extends
vs super
#
對於List<? super Integer> l1
:#
- 正確的理解:
? super Integer
限定的是泛型參數。令l1
的泛型參數是T
, 則T
是Integer
或Integer
的父類,因此Integer
或Integer
的子類的對象就可以添加到l1
中. - 錯誤的理解:
? super Integer
限定的是插入的元素的類型,因此只要是Integer
或Integer
的父類的對象都可以插入l1
中。
對於List<? extends Integer> l2:
#
- 正確的理解:
? extends Integer
限定的是泛型參數。令l2
的泛型參數是T
, 則T
是Integer
或Integer
的子類,進而我們就不能找到一個類X
, 使得X
是泛型參數T
的子類,因此我們就不可以向l2
中添加元素。不過由於我們知道了泛型參數T
是Integer
或Integer
的子類這一點,因此我們就可以從l2
中讀取到元素 (取到的元素類型是Integer
或Integer
的子類), 並可以存放到Integer
中。 - 錯誤的理解:
? extends Integer
限定的是插入元素的類型,因此只要是Integer
或Integer
的子類的對象都可以插入l2
中
應用:PECS#
PECS 原則: Producer Extends, Consumer Super
- Producer extends: 如果我們需要一個
List
提供類型為T
的數據 (即希望從List
中讀取T
類型的數據), 那麼我們需要使用? extends T
, 例如List<? extends Integer>
。 但是我們不能向這個List
添加數據。 - Consumer Super: 如果我們需要一個
List
來消費T
類型的數據 (即希望將T
類型的數據寫入List
中), 那麼我們需要使用? super T
, 例如List<? super Integer>
。但是這個List
不能保證從它讀取的數據的類型。 - 如果我們既希望讀取,也希望寫入,那麼我們就必須明確地聲明泛型參數的類型,例如 List。
public class Collections {
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (int i = 0; i < src.size(); i++) {
dest.set(i, src.get(i));
}
}
}