何时在 Java 泛型中使用通配符?

2022-09-01 03:08:54

这是来自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)

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


答案 1

两者之间的巨大区别

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

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

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

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

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

编辑:

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


答案 2

通配符是关于泛型的共对方差。我将通过提供一些例子来阐明这意味着什么。

基本上,它与以下事实有关:对于类型 S 和 T,其中 S 是 T 的子类型,泛型类型不是G<S>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不是详细讨论这个问题的正确地方。我将尝试找到一些更详细地解释这一点的文章和论文。


推荐