Java™ 教程(泛型通配符)

泛型通配符

在泛型代碼中,稱爲通配符的問號()表示未知類型,通配符可用於各類狀況:做爲參數、字段或局部變量的類型,有時做爲返回類型(儘管更好的編程實踐是更加具體),通配符從不用做泛型方法調用、泛型類實例建立或超類型的類型參數。html

如下部分更詳細地討論通配符,包括上界通配符、下界通配符和通配符捕獲。java

上界通配符

你可使用上界通配符來放寬對變量的限制,例如,假設你要編寫一個適用於List<Integer>List<Double>List<Number>的方法,你能夠經過使用上界通配符來實現這一點。編程

要聲明一個上界通配符,請使用通配符('?'),後跟extends關鍵字,後跟上界,請注意,在此上下文中,extends在通常意義上用於表示「extends」(如在類中)或「implements」(如在接口中)。segmentfault

要編寫適用於NumberNumber的子類型列表的方法,例如IntegerDoubleFloat,你能夠指定List<?extends Number>List<Number>一詞比List<? extends Number>更具限制性,由於前者只匹配Number類型的列表,然後者匹配Number類型或其任何子類的列表。api

考慮如下process方法:數組

public static void process(List<? extends Foo> list) { /* ... */ }

上界通配符<? extends Foo>,其中Foo是任何類型,匹配FooFoo的任何子類型,process方法能夠像Foo類型同樣訪問列表元素:oracle

public static void process(List<? extends Foo> list) {
    for (Foo elem : list) {
        // ...
    }
}

foreach子句中,elem變量遍歷列表中的每一個元素,如今能夠在elem上使用Foo類中定義的任何方法。spa

sumOfList方法返回列表中數字的總和:code

public static double sumOfList(List<? extends Number> list) {
    double s = 0.0;
    for (Number n : list)
        s += n.doubleValue();
    return s;
}

如下代碼使用Integer對象列表打印sum = 6.0htm

List<Integer> li = Arrays.asList(1, 2, 3);
System.out.println("sum = " + sumOfList(li));

Double值列表可使用相同的sumOfList方法,如下代碼打印sum = 7.0

List<Double> ld = Arrays.asList(1.2, 2.3, 3.5);
System.out.println("sum = " + sumOfList(ld));

無界通配符

使用通配符(?)指定無界通配符類型,例如List<?>,這稱爲未知類型的列表,有兩種狀況,無界通配符是一種有用的方法:

  • 若是你正在編寫可使用Object類中提供的功能實現的方法。
  • 當代碼使用泛型類中不依賴於類型參數的方法時,例如,List.sizeList.clear,事實上,常常使用Class<?>,由於Class<T>中的大多數方法都不依賴於T

考慮如下方法,printList

public static void printList(List<Object> list) {
    for (Object elem : list)
        System.out.println(elem + " ");
    System.out.println();
}

printList的目標是打印任何類型的列表,但它沒法實現該目標 — 它只打印一個Object實例列表,它不能打印List<Integer>List<String>List<Double>等,由於它們不是List<Object>的子類型,要編寫通用的printList方法,請使用List<?>

public static void printList(List<?> list) {
    for (Object elem: list)
        System.out.print(elem + " ");
    System.out.println();
}

由於對於任何具體類型AList<A>List<?>的子類型,你可使用printList打印任何類型的列表:

List<Integer> li = Arrays.asList(1, 2, 3);
List<String>  ls = Arrays.asList("one", "two", "three");
printList(li);
printList(ls);
在本課程的示例中使用了 Arrays.asList方法,此靜態工廠方法轉換指定的數組並返回固定大小的列表。

重要的是要注意List<Object>List<?>是不同的,你能夠將ObjectObject的任何子類型插入List<Object>,可是你只能在List<?>中插入null,通配符使用指南部分提供了有關如何肯定在給定狀況下應使用哪一種通配符(若是有)的更多信息。

下界通配符

上界通配符部分顯示上界通配符將未知類型限制爲該類型的特定類型或子類型,並使用extends關鍵字表示,以相似的方式,下界通配符將未知類型限制爲該類型的特定類型或超類型。

使用通配符(?)表示下界通配符,後跟super關鍵字,後跟下界:<? super A>

你能夠指定通配符的上界,也能夠指定下界限,但不能同時指定二者。

假設你要編寫一個將Integer對象放入列表的方法,爲了最大限度地提升靈活性,你但願該方法能夠處理List<Integer>List<Number>List<Object> — 任何能夠保存Integer值的方法。

要編寫適用於IntegerInteger超類型列表的方法,例如IntegerNumberObject,你能夠指定List<? super Integer>List<Integer>一詞比List<? super Integer>更具限制性,由於前者僅匹配Integer類型的列表,然後者匹配任何類型爲Integer的超類型的列表。

如下代碼將數字1到10添加到列表的末尾:

public static void addNumbers(List<? super Integer> list) {
    for (int i = 1; i <= 10; i++) {
        list.add(i);
    }
}

通配符和子類型

泛型、繼承和子類型中所述,泛型類或接口不相關,僅僅由於他們的類型之間存在關係,可是,你可使用通配符在泛型類或接口之間建立關係。

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

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

編寫如下代碼是合理的:

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

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

List<B> lb = new ArrayList<>();
List<A> la = lb;   // compile-time error

假設IntegerNumber的子類型,List<Integer>List<Number>之間的關係是什麼?

generics-listParent.gif

雖然IntegerNumber的子類型,但List<Integer>不是List<Number>的子類型,事實上,這兩種類型不相關,List<Number>List<Integer>的公共父級是List<?>

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

List<? extends Integer> intList = new ArrayList<>();
List<? extends Number>  numList = intList;  // OK. List<? extends Integer> is a subtype of List<? extends Number>

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

generics-wildcardSubtyping.gif


上一篇:類型推斷

下一篇:泛型通配符捕獲和Helper方法

相關文章
相關標籤/搜索