我正在刷新关于 Java 泛型的知识。所以我转向了 Oracle 的优秀教程……并开始为我的同事准备一个演示文稿。我在 tutorial 中遇到了通配符部分。上面写着:
Consider the following method, printList:
public static void printList(List<Object> list) { ...
The goal of printList is to print a list of any type, but it fails to achieve that goal — it prints only a list of Object instances; it cannot print
List<Integer>
,List<String>
,List<Double>
, and so on, because they are not subtypes ofList<Object>
. To write a generic printList method, useList<?>
:public static void printList(List<?> list) {
我了解 List<Object>
不管用;但我将代码更改为
static <E> void printObjects(List<E> list) {
for (E e : list) {
System.out.println(e.toString());
}
}
...
List<Object> objects = Arrays.<Object>asList("1", "two");
printObjects(objects);
List<Integer> integers = Arrays.asList(3, 4);
printObjects(integers);
你猜怎么着;使用 List<E>
我可以毫无问题地打印不同类型的列表。
长话短说:至少教程表明一个需要通配符来解决这个问题;但如图所示,也可以通过这种方式解决。那么,我错过了什么?!
(旁注:使用 Java7 测试;所以这可能是 Java5、Java6 的问题;但另一方面,Oracle 似乎在更新他们的教程方面做得很好)
最佳答案
您使用泛型方法的方法比使用通配符的版本更强大,所以是的,您的方法也是可能的。但是,本教程没有声明使用通配符是唯一可能的解决方案,因此本教程也是正确的。
与泛型方法相比,您使用通配符获得的好处:您必须编写更少的代码,并且界面“更干净”,因为非泛型方法更容易掌握。
为什么泛型方法比通配符方法更强大:你给参数一个你可以引用的名字。例如,考虑一个删除列表的第一个元素并将其添加到列表后面的方法。使用泛型参数,我们可以做到以下几点:
static <T> boolean rotateOneElement(List<T> l){
return l.add(l.remove(0));
}
使用通配符,这是不可能的,因为 l.remove(0)
会返回 capture-1-of-?
,但是 l.add
需要 capture-2-of-?
。即,编译器无法推断出 remove
的结果与 add
期望的类型相同。这与第一个例子相反,编译器可以推断出两者都是相同类型的T
。此代码无法编译:
static boolean rotateOneElement(List<?> l){
return l.add(l.remove(0)); //ERROR!
}
那么,如果你想要一个带有通配符的 rotateOneElement 方法,你可以怎么做,因为它比通用解决方案更容易使用?答案很简单:让通配符方法调用泛型方法,然后就可以了:
// Private implementation
private static <T> boolean rotateOneElementImpl(List<T> l){
return l.add(l.remove(0));
}
//Public interface
static void rotateOneElement(List<?> l){
rotateOneElementImpl(l);
}
标准库在很多地方都使用了这个技巧。其中之一是 IIRC、Collections.java
关于Java 泛型 : wildcard<? > vs 类型参数<E>?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/22860582/