java泛型通配符


考慮到常規打印集合所有元素的問題,然而你將寫一個比較好的版本(jdk1.5以前)

void printCollection(Collection c) {
    Iterator i = c.iterator();
    for (k = 0; k < c.size(); k++) {
        System.out.println(i.next());
    }
}
然而有一個幼稚的想法去使用泛型(使用新的for)
void printCollection(Collection<Object> c) {
    for (Object e : c) {
        System.out.println(e);
    }
}
問題是,這種新的版本的使用比舊版本使用少得多。其中,作爲舊代碼可以與任何種類的集合作爲參數調用,而新的代碼只能使用Collection<Object>,其中,因爲我們剛剛的演示,不是所有類型的集合的超類型

哪什麼纔是任何集合類型的超類型?它應該被寫成Collection<?>(表示集合類型未知), 這樣可以匹配任何類型. 
這就是所謂的通配符類型,原因很明顯。我們可以這樣寫
void printCollection(Collection<?> c) {
    for (Object e : c) {
        System.out.println(e);
    }
}
然而,現在我們調用任何集合類型。注意printCollection()的內部我們可以一直讀取元素並賦給Object類型,這是安全的。因爲任何集合的實際類型,它包含對象。它不安全的是不能任意添加元素。
Collection<?> c = new ArrayList<String>();
c.add(new Object()); // Compile time error
c.add(null);//OK  because null is any type

由於我們不知道c代表的是什麼元素,我們不能向集合添加對象。add()方法裏面的參數爲E,也是集合類型,當使用?作爲參數是,它代表的類型是未知的,我們通過添加任何參數必須是這個未知類型的子類型。因爲我們不知道那是什麼類型的,我們不能傳遞任何東西。唯一的例外是null,它是每一個類型的成員。

另一方面,獲得一個List<?>,我們可以通過調用get()方法獲取和使用元素,這個元素的類型是未知的,但是我們總知道它是一個對象,將get()的結果賦值給Object類型的變量時安全的把它作爲其中類型對象期望的參數。

邊界通配符
考慮一個簡單的畫圖一樣,能夠花的圖形例如矩形和圓形。類的繼承結構如下:
public abstract class Shape {
    public abstract void draw(Canvas c);
}


public class Circle extends Shape {
    private int x, y, radius;
    public void draw(Canvas c) {
        ...
    }
}


public class Rectangle extends Shape {
    private int x, y, width, height;
    public void draw(Canvas c) {
        ...
    }
}

這些類都能在畫布上畫圖:
public class Canvas {
    public void draw(Shape s) {
        s.draw(this);
   }
}


任何繪圖通常含有多種形狀的。假設他們表示爲一個列表,這將是方便的在Canvas的方法,吸引了所有這些:
public void drawAll(List<Shape> shapes) {
    for (Shape s: shapes) {
        s.draw(this);
   }
}


現在,類型規則說drawAll()只能算得上準確形狀的名單:它不能,例如,算得上一個List <Circle>。這是不幸的,因爲所有的方法不會被從列表中讀出的形狀,所以它也可以同樣可以在一個List <Circle>調用。我們真正需要的是接受任何一種形狀的列表的方法:
public void drawAll(List<? extends Shape> shapes) {
    ...
}


這是聰明的,但是非常重要的地方:我們是用List<? extends Shape>替換了List<Shape>。現在drawAll() 可以接受Shape的子類類型,如果我們想使用,我們現在就可以使用List<Circle>調用。
List<? extends Shape> 是一個邊界通配的示例。而這個?代表的未知類型,就像以前看到的通配符。然而,例如,我們雖然知道那個未知類型是Shape的子類型。(Note:它可能是自身或它的子類,不需要字面繼承Shape)我們說Shape是通配符的上界。
還有就是,像往常一樣,價格要支付使用通配符的靈活性。價格是它現在是非法寫入形狀的方法。例如,這是不允許的:
public void addRectangle(List<? extends Shape> shapes) {
    // Compile-time error!
    shapes.add(0, new Rectangle());
}


你應該能夠找出爲什麼上面的代碼是不允許的。shapes.add()的第二個參數是什麼?繼承Shape--一個Shape的未知類型。因爲我們不知道它的類型是什麼,如果它是一個Rectangle的父類型,它可能或不可能是一個父類型,因此它添加Rectangle是不安全的。

原文鏈接:https://docs.oracle.com/javase/tutorial/extra/generics/wildcards.html

發佈了298 篇原創文章 · 獲贊 9 · 訪問量 24萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章