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

时间:2010-08-15 08:19:09

标签: java api generics bounded-wildcard

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

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

5 个答案:

答案 0 :(得分:110)

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

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's Java Generics FAQs

的引用
  

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

     

通配符只能有一个绑定,而类型参数可以有多个边界。   通配符可以具有下限或上限,而不存在类型参数的下限。

     

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

     

<强>语法

  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,例如CircleList<Object>。这显然是类型安全的,但我们的原始声明不够灵活,不允许它。

相关问题


摘要

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

答案 1 :(得分:5)

在您的示例中,您实际上不需要使用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传递给仅采用Squares的方法。如果你用'?'你必须将生成的Shape转换为Square,这不是类型安全的。

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

答案 2 :(得分:1)

请考虑下面的James Gosling第4版的Java编程示例,我们要合并2个SinglyLinkQueue:

public static <T1, T2 extends T1> void merge(SinglyLinkQueue<T1> d, SinglyLinkQueue<T2> s){
    // merge s element into d
}

public static <T> void merge(SinglyLinkQueue<T> d, SinglyLinkQueue<? extends T> s){
        // merge s element into d
}

上述两种方法都具有相同的功能。那么哪个更好?答案是第二个。用作者自己的话来说:

&#34;一般规则是尽可能使用通配符,因为带有通配符的代码 通常比具有多个类型参数的代码更具可读性。在决定是否需要类型时 变量,问问自己该类型变量是用于关联两个或多个参数,还是用于关联参数 返回类型的类型。如果答案为否,那么通配符就足够了。&#34;

注意:在书中只给出第二种方法,类型参数名称是S而不是&#39; T&#39;。第一种方法不在书中。

答案 3 :(得分:1)

据我所知,通配符允许在不需要类型参数的情况下使用更简洁的代码(例如,因为它在多个地方被引用,或者因为需要多个边界,如其他答案中详述的那样)。

在链接中,您指出我读过(在“通用方法”下)以下有关此方向的陈述:

  

通用方法允许使用类型参数来表达   方法的一个或多个参数的类型之间的依赖关系   和/或其返回类型。如果没有这样的依赖,那就是泛型   方法不应该使用。

     

[...]

     

使用通配符比声明显式更清晰,更简洁   类型参数,因此应尽可能优先。

     

[...]

     

通配符还具有可以在外面使用的优点   方法签名,作为字段类型,局部变量和数组。

答案 4 :(得分:0)

第二种方式有点冗长,但它允许你在其中引用T

for (T shape : shapes) {
    ...
}

据我所知,这是唯一的区别。