Java 泛型:通配符<?> vs 类型参数<E>?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/22860582/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-13 18:27:26  来源:igfitidea点击:

Java generics: wildcard<?> vs type parameter<E>?

javagenerics

提问by GhostCat

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 tutorialthat says:

我正在刷新我对 Java 泛型的了解。所以我转向了 Oracle 的优秀教程……并开始为我的同事整理一份演示文稿。我在教程中看到有关通配符的部分说:

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

考虑以下方法,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) {

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

我知道这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);

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

你猜怎么着; 使用List<E>我可以毫无问题地打印不同类型的列表。

Long story short: at least the tutorial indicates that one needsthe 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)

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

采纳答案by gexicide

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 notstate 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.addwould require capture-2-of-?. I.e., the compiler is not able to deduce that the result of removeis the same type that addexpects. This is contrary to the first example where the compiler can deduce that both is the same type T. This code would not compile:

使用通配符,这是不可能的,因为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!
}

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:

那么,如果您想要一个带有通配符的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);
}

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

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

回答by Djizeus

Both solutions are effectively the same, it's just that in the second one you are naming the wildcard. This can come handy when you want to use the wildcard several times in the signature, but want to make sure that both refer to the same type:

两种解决方案实际上是相同的,只是在第二个解决方案中您命名了通配符。当您想在签名中多次使用通配符,但又想确保两者都引用相同的类型时,这会派上用场:

static <E> void printObjects(List<E> list, PrintFormat<E> format) {

回答by Tanmay Patil

Technically, there is no differencebetween

从技术上讲,两者之间没有区别

<E> void printObjects(List<E> list) {

and

void printList(List<?> list) {


  • When you are declaring a type parameter, and using it only once, it essentially becomes a wildcard parameter.
  • On the other hand, if you use it more than once, the difference becomes significant. e.g.

    <E> void printObjectsExceptOne(List<E> list, E object) {
    

    is completely different than

    void printObjects(List<?> list, Object object) {
    

    You might see that first case enforcesboth types to be same. While there is no restriction in second case.

  • 当您声明一个类型参数并且只使用它一次时,它实际上变成了一个通配符参数。
  • 另一方面,如果您多次使用它,差异就会变得显着。例如

    <E> void printObjectsExceptOne(List<E> list, E object) {
    

    完全不同于

    void printObjects(List<?> list, Object object) {
    

    您可能会看到第一种情况强制两种类型相同。虽然在第二种情况下没有限制。



As a result, if you are going to use a type parameter only once, it does not even make sense to name it. That is why java architects invented so called wildcard arguments (most probably).

因此,如果您只打算使用一个类型参数一次,那么命名它甚至没有意义。这就是为什么 Java 架构师发明了所谓的通配符参数(很可能)。

Wildcard parameters avoid unnecessary code bloat and make code more readable. If you need two, you have to fall back to regular syntax for type parameters.

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

Hope this helps.

希望这可以帮助。