在泛型代碼中,稱爲通配符的問號(?
)表示未知類型,通配符可用於各類狀況:做爲參數、字段或局部變量的類型,有時做爲返回類型(儘管更好的編程實踐是更加具體),通配符從不用做泛型方法調用、泛型類實例建立或超類型的類型參數。html
如下部分更詳細地討論通配符,包括上界通配符、下界通配符和通配符捕獲。java
你可使用上界通配符來放寬對變量的限制,例如,假設你要編寫一個適用於List<Integer>
、List<Double>
和List<Number>
的方法,你能夠經過使用上界通配符來實現這一點。編程
要聲明一個上界通配符,請使用通配符('?
'),後跟extends
關鍵字,後跟上界,請注意,在此上下文中,extends
在通常意義上用於表示「extends」(如在類中)或「implements」(如在接口中)。segmentfault
要編寫適用於Number
和Number
的子類型列表的方法,例如Integer
、Double
和Float
,你能夠指定List<?extends Number>
,List<Number>
一詞比List<? extends Number>
更具限制性,由於前者只匹配Number
類型的列表,然後者匹配Number
類型或其任何子類的列表。api
考慮如下process
方法:數組
public static void process(List<? extends Foo> list) { /* ... */ }
上界通配符<? extends Foo>
,其中Foo
是任何類型,匹配Foo
和Foo
的任何子類型,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.0
:htm
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.size
或List.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(); }
由於對於任何具體類型A
,List<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<?>
是不同的,你能夠將Object
或Object
的任何子類型插入List<Object>
,可是你只能在List<?>
中插入null
,通配符使用指南部分提供了有關如何肯定在給定狀況下應使用哪一種通配符(若是有)的更多信息。
上界通配符部分顯示上界通配符將未知類型限制爲該類型的特定類型或子類型,並使用extends
關鍵字表示,以相似的方式,下界通配符將未知類型限制爲該類型的特定類型或超類型。
使用通配符(?
)表示下界通配符,後跟super
關鍵字,後跟下界:<? super A>
。
你能夠指定通配符的上界,也能夠指定下界限,但不能同時指定二者。
假設你要編寫一個將Integer
對象放入列表的方法,爲了最大限度地提升靈活性,你但願該方法能夠處理List<Integer>
、List<Number>
和List<Object>
— 任何能夠保存Integer
值的方法。
要編寫適用於Integer
和Integer
超類型列表的方法,例如Integer
、Number
和Object
,你能夠指定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
假設Integer
是Number
的子類型,List<Integer>
和List<Number>
之間的關係是什麼?
雖然Integer
是Number
的子類型,但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>
由於Integer
是Number
的子類型,而numList
是Number
對象的列表,因此intList
(Integer
對象列表)和numList
之間如今存在關係,下圖顯示了使用上界和下界通配符聲明的多個List
類之間的關係。