Java 泛型之上界下界通配符

Java 泛型之上界下界通配符

Java教程是爲JDK 8編寫的。本頁描述的示例和實踐沒有利用後續版本中引入的改進。java

通配符和子類型

如 泛型,繼承和子類型中所述,泛型類或接口僅僅由於它們的類型之間存在關係而無關。可是,您可使用通配符在泛型類或接口之間建立關係。程序員

給定如下兩個常規(非泛型)類:編程

class A { /* ... */ }
    class B extends A { /* ... */ }

編寫如下代碼是合理的:學習

B b = new B();
    A a = b;

此示例顯示常規類的繼承遵循此子類型規則:若是B擴展A,則類B是類A的子類型。此規則不適用於泛型類型:this

List<B> lb = new ArrayList<>();
List<A> la = lb;   //編譯時錯誤

鑑於IntegerNumber的子類型,List<Integer>List<Number> 之間的關係是什麼?spa

generics-listParent.gif
公共父類是List<?>
該圖表顯示 List<Number>List<Integer> 的公共父級是未知類型的List.設計

儘管IntegerNumber的子類型,但List<Integer>不是List<Number>的子類型,實際上,這兩種類型不相關。List<Number>List<Integer> 的公共父是 List<?>code

上界(extends)的通配符與下界(super)通配符

上界(extends)的通配符意即該類的父類中包含指定類.對象

下界(super)通配符意即該類的子類中包含指定類.繼承

爲了在這些類之間建立關係以便代碼能夠經過 List<Integer> 的元素訪問Number的方法,請使用上界的通配符:

List<? extends Integer> intList = new ArrayList<>();
List<? extends Number>  numList = intList;  // OK, List<?extends Integer>是 List< ? extends Number>的子類型

由於IntegerNumber的子類型,而numListNumber對象的列表,因此intList(是一個Integer對象列表)和numList之間如今存在關係。下圖顯示了使用上限和下限通配符聲明的多個 List 類之間的關係。

generics-wildcardSubtyping.gif
幾個通用List類聲明的層次結構。

圖表顯示 List<Integer>List <? extends Integer>List<?super Integer>的子類型。 List<? extends Integer>List<? extends Number> 的子類型,它是List <?>的子類型。 List<Number>List <?super Number>List<? extends Number>的子類型。 List<? super Number>List <? super Integer>的子類型, ft且都是List<?>的子類型。

通配符使用指南

學習使用泛型編程時,更使人困惑的一個方面是肯定什麼時候使用上限有界通配符以及什麼時候使用下限有界通配符。本文提供一些設計代碼時要遵循的一些準則。

爲討論方便,認爲變量具有兩個功能:

一個「In」變量

「in」變量向代碼提供數據。想象一下帶有兩個參數的複製方法:copy(src,dest)。該SRC參數提供的數據被複制,所以它是「in」參數。

一個「Out」變量

「out」變量保存數據以供其餘地方使用。在複製示例中,copy(src,dest),dest參數接受數據,所以它是「out」參數。
固然,一些變量既用於「in」又用於「out」目的 - 這種狀況也在本文中也用到了。

在決定是否使用通配符以及適合使用哪一種類型的通配符時,可使用「in」和「out」原則。如下列表提供了遵循的準則:

通配符指南:

  • 使用extends關鍵字, 定義帶有上界通配符的「in」變量。
  • 使用super關鍵字,使用下界通配符定義「out」變量。
  • 在可使用Object類中定義的方法訪問「in」變量的狀況下,使用無界通配符。
  • 在代碼須要做爲「in」和「out」變量訪問變量的狀況下,不要使用通配符。

這些指南不適用於方法的返回類型。應該避免使用通配符做爲返回類型,由於它強制程序員使用代碼來處理通配符。

List<? extends ...> 能夠被非正式地認爲是隻讀的,但這不是一個嚴格的保證。假設您有如下兩個類:

class NaturalNumber {

    private int i;

    public NaturalNumber(int i) { this.i = i; }
    // ...
}

class EvenNumber extends NaturalNumber {

    public EvenNumber(int i) { super(i); }
    // ...
}

請考慮如下代碼:

List<EvenNumber> le = new ArrayList<>();
List<? extends NaturalNumber> ln = le;
ln.add(new NaturalNumber(35));  // compile-time error //編譯時錯誤

由於List<EvenNumber>List<? extends NaturalNumber>,您能夠賦值leln。可是你不能使用ln將天然數添加到偶數列表中。列表中的如下操做是可能的:

  • 您能夠添加null。
  • 你能夠調用清除。
  • 您能夠獲取迭代器並調用remove。
  • 您能夠捕獲通配符並寫入從列表中讀取的元素。

你能夠看到List<? extends NaturalNumber>在嚴格意義上不是隻讀的,但您可能會這樣想,由於您沒法存儲新元素或更改列表中的現有元素。

相關文章
相關標籤/搜索