我正在刷新我对Java泛型的知识。所以我转向Oracle的优秀教程......并开始为我的同事准备一份演示文稿。我在tutorial中看到了有关通配符的部分,其中说:
考虑以下方法,printList:
public static void printList(List<Object> list) { ...
printList的目标是打印任何类型的列表,但它无法实现该目标 - 它只打印一个Object实例列表;它无法打印
List<Integer>
,List<String>
,List<Double>
等,因为它们不是List<Object>
的子类型。要编写通用的printList方法,请使用List<?>
: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似乎在他们的教程更新方面做得很好)
答案 0 :(得分:38)
使用通用方法的方法比使用通配符的方法更强大,所以是的,您的方法也是可行的。但是,本教程不表明使用通配符是唯一可行的解决方案,因此本教程也是正确的。
与通用方法相比,使用通配符获得的结果是:由于非通用方法更易于掌握,因此必须编写更少且界面更“清晰”。
为什么泛型方法比通配符方法更强大:为参数指定一个可以引用的名称。例如,考虑一种方法,该方法删除列表的第一个元素并将其添加到列表的后面。使用通用参数,我们可以执行以下操作:
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
答案 1 :(得分:7)
两种解决方案实际上是相同的,只是在第二种解决方案中命名通配符。当你想在签名中多次使用通配符时,这可以派上用场,但是要确保两者都引用相同的类型:
static <E> void printObjects(List<E> list, PrintFormat<E> format) {
答案 2 :(得分:6)
从技术上讲,
之间存在无差异<E> void printObjects(List<E> list) {
和
void printList(List<?> list) {
另一方面,如果您多次使用它,差异会变得很大。 e.g。
<E> void printObjectsExceptOne(List<E> list, E object) {
与
完全不同void printObjects(List<?> list, Object object) {
您可能会看到第一种情况强制执行两种类型相同。虽然第二种情况没有限制。
因此,如果您打算仅使用类型参数 ,则命名甚至没有意义。这就是为什么java架构师发明了所谓的通配符参数(最有可能)。
通配符参数可避免不必要的代码膨胀并使代码更具可读性。如果您需要两个,则必须回退到类型参数的常规语法。
希望这有帮助。