Java泛型:通配符<! - ? - > vs类型参数<e>?</e>

时间:2014-04-04 10:44:28

标签: java generics

我正在刷新我对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似乎在他们的教程更新方面做得很好)

3 个答案:

答案 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架构师发明了所谓的通配符参数(最有可能)。

通配符参数可避免不必要的代码膨胀并使代码更具可读性。如果您需要两个,则必须回退到类型参数的常规语法。

希望这有帮助。