Java泛型:通配符<?> vs type参数< E&gt ;? [英] Java generics: wildcard<?> vs type parameter<E>?

查看:144
本文介绍了Java泛型:通配符<?> vs type参数< E&gt ;?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我刷新了我对Java泛型的知识。于是我转向Oracle的优秀教程...并开始为我的同事制作演示文稿。我在教程中找到了有关通配符的部分,其中提到:


考虑以下方法printList:

  public static void printList(List< Object> list){
...

printList的目标是打印任何类型的列表,但是它无法实现该目标 - 它只打印一个Object实例列表;它无法打印 List< Integer> List< String> , List< Double> 等等,因为它们不是 List< Object> 的子类型。要编写一个通用的printList方法,可以使用 List <?>< / code>:

  public static void printList(List<> list){


我知道 List< Object> 不起作用;但我将代码更改为

  static< E>无效printObjects(列表< E>列表){
for(E e:list){
System.out.println(e.toString());
}
}
...
List< Object> objects = Arrays。< Object> asList(1,two);
printObjects(objects);
列表<整数>整数= Arrays.asList(3,4);
printObjects(整数);

猜猜看;使用 List< E> 我可以打印不同类型的列表,而不会有任何问题。

长话短说:至少本教程指出一个需要通配符来解决这个问题;但如图所示,也可以通过这种方式解决。所以,我错过了什么?!



(注意:用Java7进行测试;所以这可能是Java5和Java6的问题;但另一方面,Oracle似乎关于他们教程的更新做得很好)

解决方案

使用泛型方法的方法比版本更加强大与通配符,所以是的,你的方法也是可能的。然而,本教程没有 声明使用通配符是唯一可能的解决方案,所以教程也是正确的。



与泛型方法相比,通配符要复杂得多:因为非泛型方法更容易掌握,所以您必须编写更少的接口并使其更干净。

为什么泛型方法比通配符方法更强大:给参数一个你可以参考的名字。例如,考虑一种删除列表的第一个元素并将其添加到列表后面的方法。使用泛型参数,我们可以执行以下操作:

  static< T> boolean rotateOneElement(List< T> 1){
return l.add(l.remove(0));
}

带有通配符,这是不可能的,因为 l .remove(0)会返回 capture-1-?,但 l.add 将需要 capture-2-of-?。即,编译器无法推断出 remove 的结果与 add 所期望的相同。这与第一个例子相反,在这个例子中编译器可以推断出它们是相同的类型 T 。此代码无法编译:

  static boolean rotateOneElement(List<?> l){
return l.add (l.remove(0)); //错误!





$ b

所以,如果你想要一个rotateOneElement方法通配符,因为它比通用解决方案更易于使用?答案很简单:让通配符方法调用通用方法,然后运行:

  //私有实现
私人静态< T> boolean rotateOneElementImpl(List< T> 1){
return l.add(l.remove(0));


//公共接口
static void rotateOneElement(List<?> l){
rotateOneElementImpl(l);
}

标准库在很多地方使用这个技巧。其中之一是,IIRC,Collections.java


I am refreshing my knowledge on Java generics. So I turned to the excellent tutorial from Oracle ... and started to put together a presentation for my coworkers. I came across the section on wildcards in the tutorial that says:

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 of List<Object>. To write a generic printList method, use List<?>:

public static void printList(List<?> list) {

I understand that List<Object> will not work; but I changed the code to

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);

And guess what; using List<E> I can print different types of Lists without any problem.

Long story short: at least the tutorial indicates that one needs the wildcard to solve this problem; but as shown, it can be solved this way too. So, what am I missing?!

(side note: tested with Java7; so maybe this was a problem with Java5, Java6; but on the other hand, Oracle seems to do a good job regarding updates of their tutorials)

解决方案

Your approach of using a generic method is strictly more powerful than a version with wildcards, so yes, your approach is possible, too. However, the tutorial does not state that using a wildcard is the only possible solution, so the tutorial is also correct.

What you gain with the wildcard in comparison to the generic method: You have to write less and the interface is "cleaner" since a non generic method is easier to grasp.

Why the generic method is more powerful than the wildcard method: You give the parameter a name which you can reference. For example, consider a method that removes the first element of a list and adds it to the back of the list. With generic parameters, we can do the following:

static <T> boolean rotateOneElement(List<T> l){
    return l.add(l.remove(0));
}

with a wildcard, this is not possible since l.remove(0) would return capture-1-of-?, but l.add would require capture-2-of-?. I.e., the compiler is not able to deduce that the result of remove is the same type that add expects. This is contrary to the first example where the compiler can deduce that both is the same type T. This code would not compile:

static boolean rotateOneElement(List<?> l){
    return l.add(l.remove(0)); //ERROR!
}

So, what can you do if you want to have a rotateOneElement method with a wildcard, since it is easier to use than the generic solution? The answer is simple: Let the wildcard method call the generic one, then it works:

// 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);
}

The standard library uses this trick in a number of places. One of them is, IIRC, Collections.java

这篇关于Java泛型:通配符&lt;?&gt; vs type参数&lt; E&gt ;?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆