什么时候在 Java 泛型中使用通配符?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16707340/
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-10-31 23:43:46  来源:igfitidea点击:

When to use wildcards in Java Generics?

javagenericswildcard

提问by Koray Tugay

this is from HeadFirst Java: ( page 575 )

这是来自HeadFirst Java:(第 575 页)

This:

这:

public <T extends Animal> void takeThing(ArrayList<T> list)

Does the same thing as this:

做同样的事情:

public void takeThing(ArrayList<? extends Animal> list)

So here is my question: if they are exactly same, why don't we write

所以这是我的问题:如果它们完全相同,我们为什么不写

public <? extends Animal> void takeThing(ArrayList<?> list)

or

或者

public void takeThing(ArrayList<T extends Animal> list)

Also, when would it be useful to use a ? instead of a T in a method declaration ( as above ) with Generics, or for a Class declaration? What are the benefits?

另外,什么时候使用 ? 而不是带有泛型的方法声明(如上)中的 T ,或用于类声明?有什么好处?

采纳答案by Werzi2001

The big difference between

之间的巨大区别

public <T extends Animal> void takeThing(ArrayList<T> list)

and

public void takeThing(ArrayList<? extends Animal> list)

is that in the former method you can refer to "T" within the method as the concrete class that was given. In the second method you cannot do this.

是在前一种方法中,您可以将方法中的“T”称为给出的具体类。在第二种方法中,您不能这样做。

Here a more complex example to illustrate this:

这里有一个更复杂的例子来说明这一点:

// here i can return the concrete type that was passed in
public <T extends Animal> Map<T, String> getNamesMap(ArrayList<T> list) {
    Map<T, String> names = new HashMap<T, String>();
    for (T animal : list) {
        names.put(animal, animal.getName()); // I assume there is a getName() method
    }
    return names;
}

// here i have to use general Animal
public Map<Animal, String> getNamesMap(ArrayList<? extends Animal> list) {
    Map<Animal, String> names = new HashMap<Animal, String>();
    for (Animal animal : list) {
        names.put(animal, animal.getName()); // I assume there is a getName() method
    }
    return names;
}

With the first method if you pass in an List of Cats you get a Map with Cat as key. The second method would always return a Map with general Animal key.

使用第一种方法,如果你传入一个猫列表,你会得到一个以猫为键的地图。第二种方法将始终返回带有通用 Animal 键的 Map。

By the way this is not valid java syntax:

顺便说一句,这不是有效的 java 语法:

public <? extends Animal> void takeThing(ArrayList<?> list)

Using this form of generic method declaration you have to use a valid java identifier and not "?".

使用这种形式的泛型方法声明,您必须使用有效的 java 标识符而不是“?”。

Edit:

编辑:

The form "? extends Type" only applies to variable or parameter type declaration. Within a generic method declration it has to be "Identifier extends Type" as you are able to refer to the "Identifier" from within your method.

形式“? extends Type”仅适用于变量或参数类型声明。在通用方法声明中,它必须是“标识符扩展类型”,因为您可以在方法中引用“标识符”。

回答by bennidi

Wild cards are about co/contra variance of generics. I will try to make clear what this means by providing some examples.

通配符是关于泛型的 co/contra 方差。我将尝试通过提供一些示例来阐明这意味着什么。

Basically it is related to the fact that for types S and T, where S is a subtype of T, a generic type G<S>is not a valid subtype of G<T>

基本上它与以下事实有关,对于类型 S 和 T,其中 S 是 T 的子类型,泛型类型G<S>不是T的有效子类型G<T>

List<Number> someNumbers = new ArrayList<Long>(); // compile error

You can remedy this with wild cards

你可以用通配符解决这个问题

List<? extends Number> someNumbers = new ArrayList<Long>(); // this works

Please note, that you can not put anything into such a list

请注意,您不能将任何内容放入这样的列表中

someNumbers.add(2L); //compile error

even (and more surprising for many developers):

甚至(对于许多开发人员来说更令人惊讶):

List<? extends Long> someLongs = new ArrayList<Long>();
someLongs.add(2L); // compile error !!!

I think SO is not the right place to discuss that in detail. I will try to find some of the articles and papers that explain this in more detail.

我认为 SO 不是详细讨论这个问题的合适场所。我将尝试找到一些更详细地解释这一点的文章和论文。

回答by Mark Peters

Binding the type to a type parameter can be more powerful, depending on what the method is supposed to do. I'm not sure what takeThingis supposed to do, but imagine in general we have a method with one of these type signatures:

将类型绑定到类型参数可以更强大,具体取决于方法应该做什么。我不确定takeThing应该做什么,但想象一下,我们有一个具有以下类型签名之一的方法:

public <T extends Animal> void foo(ArrayList<T> list);

//or

public void foo(ArrayList<? extends Animal> list);

Here's a concrete example of something you can only do with the firsttype signature:

这是您只能使用第一个类型签名执行的操作的具体示例:

public <T extends Animal> void foo(ArrayList<T> list) {
    list.add(list.remove(0)); // (cycle front element to the back)
} 

In this case Tis required to inform the type checker that the element being removedfrom the list is an OK element to addto the list.

在这种情况下T,需要通知类型检查器从列表中删除的元素是添加到列表中的 OK 元素。

You could not do this with a wildcard because, as the wildcard has not been bound to a type parameter, its context is not tracked (well, it is tracked, through "captures", but it's not available to leverage). You can get more information on this in another answer I've given: How do generics of generics work?

您不能使用通配符执行此操作,因为由于通配符尚未绑定到类型参数,因此不会跟踪其上下文(好吧,它是通过“捕获”进行跟踪的,但无法利用)。您可以在我给出的另一个答案中获得更多信息:泛型的泛型如何工作?

回答by Peter Becker

If you write ? extends Tyou say "anything that is a T or more specific". For example: a List<Shape>can have only Shapes in it, while a List<? extends Shape>can have Shapes, Circles, Rectangles, etc.

如果你写? extends T你说“任何是 T 或更具体的东西”。例如:a 中List<Shape>只能有Shapes,而 a 中List<? extends Shape>可以有Shapes、Circles、Rectangles 等。

If you write ? super Tyou say "anything that is a T or more general". This is less often used, but has it's use cases. A typical example would be a callback: if you want to pass a Rectangleback to a callback, you can use Callback<? super Rectangle>, since a Callback<Shape>will be able to handle Rectangles as well.

如果你写? super T你说“任何是 T 或更一般的东西”。这不太常用,但有它的用例。一个典型的例子是回调:如果你想将一个Rectangle返回值传递给回调,你可以使用Callback<? super Rectangle>,因为 aCallback<Shape>也可以处理Rectangles 。

Here's the relevant Wikipedia article.

这是相关的维基百科文章

回答by dcernahoschi

If your takeThingmethod needs to add elements to the listparameter, the wildcard version will not compile.

如果您的takeThing方法需要向list参数添加元素,通配符版本将无法编译。

The interesting case is when you are not adding to the list and both versions seem to compile and work.

有趣的情况是当您没有添加到列表中并且两个版本似乎都可以编译和工作时。

In this case, you would write the wildcard version when you want to allow different type of animals in the list (more flexibility) and the parameter version when you require a fixed type of animal in the list: the T type.

在这种情况下,当您希望在列表中允许不同类型的动物(更灵活)和参数版本时,您将编写通配符版本,当您需要列表中的固定类型动物时:T 类型。

For example the java.util.Collectiondeclares:

例如java.util.Collection声明:

interface Collection<E> {
  ...
  public boolean containsAll(Collection<?> c);
  ...
}

And suppose you have the following code:

并假设您有以下代码:

Collection<Object> c = Arrays.<Object>asList(1, 2); 
Collection<Integer> i = Arrays.<Integer>asList(1, 2, 3); 
i.containsAll(c); //compiles and return true as expected

If the java.util.Collectionwould be:

如果java.util.Collection是:

interface Collection<E> {
    ...
    public boolean containsAll(Collection<E> c);
    ...
}

The above test code would not compile and the flexibility of the CollectionAPI would be reduced.

上面的测试代码将无法编译,CollectionAPI的灵活性会降低。

It worth noting that the latter definition of containsAllhas the advantage of catching more errors at compile time, for example:

值得注意的是,后面的定义containsAll具有在编译时捕获更多错误的优点,例如:

Collection<String> c = Arrays.asList("1", "2"); 
Collection<Integer> i = Arrays.asList(1, 2, 3); 
i.containsAll(c); //does not compile, the integer collection can't contain strings

But misses the valid test with a Collection<Object> c = Arrays.<Object>asList(1, 2);

但是错过了有效的测试 Collection<Object> c = Arrays.<Object>asList(1, 2);

回答by Rahul Saini

Java Generics Wildcards usage is governed by the GET-PUT Principle (Which is also known as the IN-OUT principle). This states that: Use an "extends" wildcard when you only get values out of a structure, Use a "super" wildcard when you only put values into a structure, and do not use wildcards when you do both.This does not apply to a method's return type. Do not use a wildcard as a return type. See example below:

Java 泛型通配符的使用受 GET-PUT 原则(也称为 IN-OUT 原则)的约束。这说明: 当您只从结构中获取值时使用“扩展”通配符,当您只将值放入结构时使用“超级”通配符,并且当您同时执行这两种操作时不要使用通配符。这不适用于方法的返回类型。不要使用通配符作为返回类型。请参阅下面的示例:

public static<T> void copyContainerDataValues(Container<? extends T> source, Container<? super T> destinationtion){
destination.put(source.get());
}