Java:有界通配符或有界的类型参数?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (2)
  • 关注 (0)
  • 查看 (15)

最近,我阅读了这篇文章:http : //download.oracle.com/javase/tutorial/extra/generics/wildcards.html

我的问题是,而不是像这样创建一个方法:

public void drawAll(List<? extends Shape> shapes){
    for (Shape s: shapes) {
        s.draw(this);
    }
}

我可以创建一个像这样的方法,它可以正常工作:

public <T extends Shape> void drawAll(List<T> shapes){
    for (Shape s: shapes) {
        s.draw(this);
    }
}

我应该使用哪种方式?在这种情况下通配符是否有用?

提问于
用户回答回答于

这取决于你需要做什么。如果你想要这样做,你需要使用有界的类型参数:

public <T extends Shape> void addIfPretty(List<T> shapes, T shape) {
    if (shape.isPretty()) {
       shapes.add(shape);
    }
}

在这里我们有一个List<T> shapes和一个T shape,因此我们可以安全shapes.add(shape)。如果它被宣布List<? extends Shape>,你不能安全地add去(因为你可能有一个List<Square>和一个Circle)。

所以通过给一个有界的类型参数命名,我们可以选择在我们的泛型方法的其他地方使用它。当然,这些信息并不总是必需的,所以如果你不需要知道那种类型(比如你的drawAll),那么通配符就足够了。

即使你没有再次引用有界的类型参数,如果你有多个边界,仍然需要一个有界的类型参数。以下是Angelika Langer的Java Generics常见问题解答的引用

通配符绑定和绑定的类型参数有什么区别? 通配符只能有一个边界,而一个类型参数可以有多个边界。通配符可以有一个下限或上限,而类型参数的下限不存在。 通配符边界和类型参数边界经常被混淆,因为它们都被称为边界并且部分具有类似的语法。[...] 语法 type parameter bound T extends Class & Interface1 & … & InterfaceN wildcard bound upper bound ? extends SuperType lower bound ? super SubType 通配符只能有一个绑定,可以是下限或上限。通配符边界的列表是不允许的。 在一个类型参数中,可以有几个边界,但是不存在类型参数的下界。

Effective Java 2nd Edition,第28项引用:使用有界通配符提高API灵活性:

为了获得最大的灵活性,在代表生产者或消费者的输入参数上使用通配符类型 [...] PECS代表生产者extends,消费者super- 不要使用通配符类型作为返回类型。它不会为您的用户提供额外的灵活性,而是会强制他们在客户端代码中使用通配符类型。正确使用时,通配符类型对于类的用户几乎不可见。它们使方法接受它们应该接受的参数并拒绝它们应该拒绝的参数。如果类的用户必须考虑通配符类型,那么类的API可能有问题

应用PECS原理,我们现在可以回到我们的addIfPretty例子,并通过编写以下内容使其更加灵活:

public <T extends Shape> void addIfPretty(List<? super T> list, T shape) { … }

现在我们可以addIfPretty说,一个Circle,一个List<Object>。这显然是类型安全的,但我们原来的声明不够灵活以至于无法使用它。

相关问题

  • Java泛型:什么是PECS?
  • 可有人解释这是什么<? super T>意思,当它应该被使用,这种结构应如何进行合作<T><? extends T>

用户回答回答于

在你的例子中,你并不需要使用T,因为你不会在其他地方使用该类型。

但是如果你做了如下的事情:

public <T extends Shape> T drawFirstAndReturnIt(List<T> shapes){
    T s = shapes.get(0);
    s.draw(this);
    return s;
}

或者像polygenlubricants所说的,如果你想使列表中的类型参数与另一个类型参数相匹配:

public <T extends Shape> void mergeThenDraw(List<T> shapes1, List<T> shapes2) {
    List<T> mergedList = new ArrayList<T>();
    mergedList.addAll(shapes1);
    mergedList.addAll(shapes2);
    for (Shape s: mergedList) {
        s.draw(this);
    }
}

在第一个示例中,您获得更多类型安全性,然后返回Shape,因为您可以将结果传递给可能需要Shape的子项的函数。例如,您可以传递List<Square>给我的方法,然后将生成的Square传递给仅占用方块的方法。如果你使用'?' 您必须将生成的Shape转换为Square,这不会是类型安全的。

在第二个示例中,确保两个列表都具有相同的类型参数(因为每个“?”都不相同,所以您不能使用'?',因此您可以创建一个包含来自两者的所有元素的列表。

扫码关注云+社区