38

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

这个:

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

做同样的事情:

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

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

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

或者

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

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

4

6 回答 6

32

之间的巨大差异

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

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

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

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

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

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

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

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

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

编辑:

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

于 2013-05-23T07:27:06.380 回答
12

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

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

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

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

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

请注意,您不能将任何内容放入此类列表

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

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

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

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

于 2013-05-23T08:00:35.823 回答
7

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

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

//or

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

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

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

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

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

于 2013-05-23T14:19:09.393 回答
4

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

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

这是相关的维基百科文章

于 2013-05-23T07:00:50.290 回答
1

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

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

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

例如java.util.Collection声明:

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

并假设您有以下代码:

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

如果java.util.Collection是:

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

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

值得注意的是,后者的定义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

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

于 2013-05-23T07:17:37.930 回答
1

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

public static<T> void copyContainerDataValues(Container<? extends T> source, Container<? super T> destinationtion){
destination.put(source.get());
}
于 2017-04-03T16:35:30.913 回答