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


82

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

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


?是简写形式;在内部,编译器始终将其替换为类型参数;当出现编译器错误时,您将看到替代类型参数,而不是?。
无可争议的

9
纠正:我以前的评论是错误的。通配符比我想象的要复杂。
无可争议的

@irreputable虽然确实更复杂,但是您用类型参数替换它的观点是完全有效的;这只是你无法声明的。
尤金(Eugene)

如果我们看一下刚才这两种方法,因为它们是-没有什么区别,只是一个风格问题; 否则(如果您添加一些更通用的参数),情况将会改变
尤金(Eugene)

Answers:


133

这取决于您需要做什么。如果要执行以下操作,则需要使用bounded type参数:

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)。

因此,通过给有界类型参数命名,我们可以选择在通用方法的其他地方使用它。当然,并非总是需要此信息,因此,如果您不需要太多有关类型的信息(例如your drawAll),那么仅使用通配符就足够了。

即使您不再引用边界类型参数,如果您有多个边界,仍然需要边界类型参数。这是Angelika Langer的Java泛型常见问题解答中的报价

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

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

通配符界限和类型参数界限经常被混淆,因为它们既称为界限,又具有部分相似的语法。[…]

语法

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

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

通配符只能有一个界限,即下限或上限。不允许使用通配符范围列表。

相反,类型参数可以有多个界限,但是没有类型参数的下界。

从行情有效的Java第二版,第28项:使用界通配符来增加灵活性API

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

不要将通配符类型用作返回类型。与其给用户提供更多的灵活性,不如说是迫使他们在客户端代码中使用通配符类型。正确使用的通配符类型对于类的用户几乎是不可见的。它们使方法接受应接受的参数,并拒绝应拒绝的参数。如果类的用户必须考虑通配符类型,则类的API可能存在问题

应用PECS原理,我们现在可以返回到addIfPretty示例,并通过编写以下代码使其更加灵活:

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

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

相关问题


概要

  • 一定要使用有界类型参数/通配符,它​​们会增加API的灵活性
  • 如果类型需要多个参数,则别无选择,只能使用有界类型参数
  • 如果类型要求下限,则别无选择,只能使用有界通配符
  • “生产者”具有较高的界限,“消费者”具有较低的界限
  • 不要在返回类型中使用通配符

非常感谢您,您的解释非常清楚且很有启发性
Tony Le

1
@Tony:这本书还简短讨论了在没有限制的情况下如何在通配符和类型参数之间进行选择。本质上,如果类型参数在方法声明中仅出现一次,请使用通配符。又见reverse(List<?>)从JLS例如java.sun.com/docs/books/jls/third_edition/html/...
polygenelubricants

我收到以下代码的UnsupportedOperationException,<code>公共静态<T扩展Number> void add(List <?super T> list,T num){list.add(num); } </ code>
Samra

1
另外-您不能?像这样传递方法参数,doWork(? type)因为那样您就不能在方法中使用该参数。您需要使用类型参数。
Tomasz Mularczyk '16

1
@VivekVardhan您不能使用通配符创建这样的方法,这就是重点。在决定使用哪个时,是最好的解释来源之一。
尤金

6

在您的示例中,您实际上不需要使用T,因为您没有在其他任何地方使用该类型。

但是,如果您执行以下操作:

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

或像polygenlubricants所说的那样,如果要将列表中的type参数与另一个type参数进行匹配:

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子级的函数。例如,您可以将a传递List<Square>给我的方法,然后将结果Square传递给仅采用Squares的方法。如果您使用“?” 您将不得不将生成的Shape转换为Square,这是不安全的类型。

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


我相信Shape s = ...应该是T s = ...,否则return s;不应该编译。
polygenelubricants

1

考虑下面的James Gosling的Java编程第4版中的以下示例,在此我们要合并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
}

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

“一般规则是在可能的情况下使用通配符,因为带有通配符的代码通常比带有多个类型参数的代码更具可读性。在确定是否需要类型变量时,请问问自己该类型变量是否用于关联两个或多个参数,或将参数类型与返回类型相关联。如果答案为否,那么通配符就足够了。”

注意:在本书中仅给出第二种方法,类型参数名称为S而不是'T'。书中没有第一种方法。


1

据我所知,在不需要类型参数的情况下,通配符允许使用更简洁的代码(例如,因为它在多个位置被引用,或者因为在其他答案中有详细说明,所以需要多个界限)。

在链接中,您指示我(在“通用方法”下)阅读了以下暗示该方向的陈述:

通用方法允许使用类型参数来表示方法的一个或多个参数的类型和/或其返回类型之间的依赖性。如果没有这种依赖性,则不应使用通用方法。

[...]

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

[...]

通配符还具有可以在方法签名之外使用的优势,例如字段,局部变量和数组的类型。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.