Java:有界通配符还是有界类型参数?

2022-08-31 10:51:58

最近,我读了这篇文章: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);
    }
}

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


答案 1

这取决于你需要做什么。如果要执行如下操作,则需要使用有界类型参数:

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

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

因此,通过为有界类型参数指定名称,我们可以选择在泛型方法中的其他位置使用它。当然,这些信息并不总是必需的,所以如果你不需要知道那么多关于类型的信息(例如你的),那么只有通配符就足够了。drawAll

即使您不再引用有界类型参数,如果有多个边界,则仍然需要有界类型参数。这是Angelika Langer的Java Generics FAQ中的一句话

通配符绑定和类型参数绑定之间有什么区别?

一个通配符只能有一个边界,而一个类型参数可以有多个边界。通配符可以具有下限或上限,而类型参数没有下限。

通配符边界和类型参数边界经常被混淆,因为它们都被称为边界,并且部分具有相似的语法。[...]

语法

  type parameter bound     T extends Class & Interface1 & … & InterfaceN

  wildcard bound  
      upper bound          ? extends SuperType
      lower bound          ? super   SubType

通配符只能有一个边界,可以是下限,也可以是上限。不允许使用通配符边界列表。

在 constrast 中,类型参数可以有多个边界,但类型参数没有下限。

引自 Effective Java 2nd Edition, Item 28:使用有界通配符来提高 API 的灵活性

为了获得最大的灵活性,请对表示生产者或使用者的输入参数使用通配符类型。[...]PECS代表生产者,消费者 [...]extendssuper

不要使用通配符类型作为返回类型。它不会为用户提供额外的灵活性,而是会强制他们在客户端代码中使用通配符类型。如果使用得当,通配符类型对类的用户几乎不可见。它们使方法接受它们应该接受的参数,并拒绝那些它们应该拒绝的参数。如果类的用户必须考虑通配符类型,则类的 API 可能存在问题

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

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

现在我们可以,比如说,a ,到 a 。这显然是类型安全的,但我们最初的声明不够灵活,无法允许它。addIfPrettyCircleList<Object>

相关问题


总结

  • 使用有界类型参数/通配符,它们可以提高API的灵活性
  • 如果类型需要多个参数,则别无选择,只能使用有界类型参数
  • 如果类型需要下限,则别无选择,只能使用有界通配符
  • “生产者”有上限,“消费者”有下限
  • 不要在返回类型中使用通配符

答案 2

在您的示例中,您实际上并不需要使用 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 子级的函数。例如,您可以将一个 to my 方法传递,然后将生成的 Square 传递给一个只采用 Squares 的方法。如果您使用“?”,则必须将生成的形状转换为正方形,这对类型不安全。List<Square>

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


推荐