我們有一下幾個類,一個Fruit,一個Apple,Apple 繼承 Fruit,是Fruit的子類。還有一個Plate(盤子)接口,用來裝水果,ApplePlate類,專門裝蘋果
interface Plate<R>{
void set(R r);
R get();
}
class ApplePlate implements Plate<Apple> {
@Override
public void set(Apple apple) {
}
@Override
public Apple get() {
return null;
}
}
class Fruit{
}
class Apple extends Fruit{
@Override
public String toString() {
return super.toString();
}
}
我通過泛型指定了這個盤子是用來裝蘋果的
public static void main(String[] args) {
Plate<Apple> applePlate = new ApplePlate<>();
}
假如這個時候,我蘋果喫完了,我就想用這個AppPlate去裝其他水果,可不可以呢?因爲所有的水果都繼承自Fruit,所以我要把這個蘋果AppPlate強轉成裝水果的Plate,但是直接在編譯期就報錯了,無法進行強轉這是爲什麼呢?我們知道可以強轉的兩個類應該符合A is a B的關係,A才能強轉成B,那麼Apple繼承自Fruit,Apple is a Fruit是符合的,但是Plate<Apple>滿足is a Plate<Fruit> 的關係嗎,答案是否定的。給定兩種具體的類型A和B(例如Fruit和Apple), 無論A和B是否相關, MyClass<A>與MyClass<B>都沒半毛錢關係, 它們的公共父對象是Object
public static Plate<Fruit> getFruitPlate(Plate<Apple> plate){
Plate<Fruit> fruitPlate = plate; //錯誤
return fruitPlate;
}
只有滿足下邊這種關係,才能說他們有is a 的關係
上界通配符<? extends R>:能取不能存
那麼如何讓水果盤子和蘋果盤子直接發生關係呢,答案就是通配符<?>
我們把getFruitPlate方法改造一下
public static Plate<? extends Fruit> getFruitPlate(Plate<Apple> plate){
Plate<? extends Fruit> fruitPlate = plate;
return fruitPlate;
}
Plate<? extends Fruit>叫上限通配符,他是Plate<Fruit> 和Plate<Apple>的基類,所以用子類給基類賦值是行得通的。他限定的是上邊界Fruit,只有Fruit及其子類可以作爲<? extends Fruit>的子類,從而實現賦值
那麼轉成Plate<? extends Fruit>之後,我是不是可以往他裏邊放水果了,比如apple或者其他fruit類型,然後並非這樣。如何解釋呢,其實可以這樣理解,既然Plate<? extends Fruit>是Plate<Fruit> 和Plate<Apple>的基類,那麼其實他的真正類型只有在運行時才能確定,那麼所以他的類型可能是Apple也可能是Orange等等Fruit的其他子類,所以這個情況下,他的真正類型是不確定的,所以無法往裏邊set,但是有一點,無論他的真正類型是什麼,他都是一種Fruit,所以通過get方法取是可以的
public static Plate<? extends Fruit> getFruitPlate(Plate<Apple> plate){
Plate<? extends Fruit> fruitPlate = plate;
fruitPlate.set(new Apple()); //報錯
Fruit fruit = fruitPlate.get(); //正確
return fruitPlate;
}
那麼有沒有辦法破解這個問題呢,有的,反射。但是比較危險,見下圖
下界通配符<? super R>:能存不能取
下界通配符限定的是下邊界,如下圖,Plate<? super Fruit>是Plate<Fruit>的基類
我們再增加一個類型Food,水果Fruit也屬於Food的一種,所以Fruit繼承自Food
class Food{
}
class Fruit extends Food{
}
class Apple extends Fruit{
@Override
public String toString() {
return super.toString();
}
}
增加一個放Food的盤子FoodPlate
class FoodPlate implements Plate<Food> {
@Override
public void set(Food food) {
}
@Override
public Food get() {
return null;
}
}
可以看到,可以往裏邊放Apple Banana等水果,因爲無論是哪種水果,都是屬於Fruit類型,但是往外取的時候,泛型信息丟失了,只能通過Object接收了
<?> :不能存也不能取
Plate<?>其實就是Plate<? extends Object>
總結:Java泛型PECS原則
如果你只需要從集合中獲得類型T , 使用<? extends T>通配符
如果你只需要將類型T放到集合中, 使用<? super T>通配符
如果你既要獲取又要放置元素,則不使用任何通配符。例如List<Apple>
PECS即 Producer extends Consumer super, 爲了便於記憶。
爲何要PECS原則?提升了API的靈活性
應用java.util.Collections類中的copy方法
爲什麼dest要使用<? super T> 而src要使用<? extends T>?就是因爲要從src裏取出,存在dest裏
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
int srcSize = src.size();
if (srcSize > dest.size())
throw new IndexOutOfBoundsException("Source does not fit in dest");
if (srcSize < COPY_THRESHOLD ||
(src instanceof RandomAccess && dest instanceof RandomAccess)) {
for (int i=0; i<srcSize; i++)
dest.set(i, src.get(i));
} else {
ListIterator<? super T> di=dest.listIterator();
ListIterator<? extends T> si=src.listIterator();
for (int i=0; i<srcSize; i++) {
di.next();
di.set(si.next());
}
}
}