Java泛型:通配符对类型参数 ?

时间:2020-12-22 16:33:30

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:

我正在刷新我对Java泛型的知识。所以我求助于Oracle的优秀教程…然后开始为我的同事们做一个演示。在本教程中,我遇到了通配符的章节,上面写着:

Consider the following method, printList:

考虑下面的方法,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<?>:

printList的目标是打印任何类型的列表,但是它不能实现这个目标——它只打印对象实例的列表;它不能打印List , List , List ,等等,因为它们不是List 的子类型。要编写一个通用的printList方法,请使用List :

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

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

我知道List将不起作用;但是我把代码改了。

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)

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

3 个解决方案

#1


30  

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:

使用通配符后,这是不可能的,因为l.remove(0)将返回捕获-1- ?,但l。添加需要capture-2-of - ?。即。,编译器无法推断出删除的结果与添加期望的类型相同。这与第一个例子相反,编译器可以推断两者都是相同类型的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。

#2


7  

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

#3


5  

Technically, there is no difference between

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

<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 enforces both types to be same. While there is no restriction in second case.

    您可能会看到,第一个案例强制两种类型相同。第二种情况没有限制。


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.

希望这个有帮助。

#1


30  

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:

使用通配符后,这是不可能的,因为l.remove(0)将返回捕获-1- ?,但l。添加需要capture-2-of - ?。即。,编译器无法推断出删除的结果与添加期望的类型相同。这与第一个例子相反,编译器可以推断两者都是相同类型的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。

#2


7  

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

#3


5  

Technically, there is no difference between

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

<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 enforces both types to be same. While there is no restriction in second case.

    您可能会看到,第一个案例强制两种类型相同。第二种情况没有限制。


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.

希望这个有帮助。