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

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

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

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

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

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

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

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

 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> List<? extends Shape> ,你不能安全地add到它(因为你可能有一个List<Square>和一个Circle )。

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

即使你不再指向有界的types参数,如果你有多个边界,仍然需要一个有界的types参数。 这里有一个来自Angelika Langer的Javagenerics常见问题的引用

通配符绑定和绑定的types参数有什么区别?

通配符只能有一个边界,而一个types参数可以有多个边界。 通配符可以有一个下限或者一个上限,而不存在types参数的下限。

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

语法

  type parameter bound T extends Class & Interface1 & … & InterfaceN wildcard bound upper bound ? extends SuperType lower bound ? super SubType 

通配符只能有一个绑定,可以是下限或上限。 通配符边界的列表是不允许的。

在一个types参数中,可以有多个边界,但是不存在types参数的下限。

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

为了获得最大的灵活性,对代表生产者或消费者的input参数使用通配符types。 PECS代表生产者extends ,消费者super […]

不要使用通配符types作为返回types 。 这会强制用户在客户端代码中使用通配符types,而不是为用户提供额外的灵活性。 正确使用,类的用户几乎看不到通配符types。 他们使方法接受他们应该接受的参数,拒绝他们应该拒绝的参数。 如果类的用户必须考虑通配符types,那么类的API可能有问题

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

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

现在我们可以添加addIfPretty ,例如一个Circle ,到一个List<Object> 。 这显然是types安全的,但是我们原来的声明还不够灵活,

相关问题

  • Javagenerics:什么是PECS?
  • 有人可以解释什么<? super T> <? super T>是什么意思,什么时候使用,这个结构应该如何配合<T><? extends T> <? extends T>

概要

  • 使用有界的types参数/通配符,它​​们增加了API的灵活性
  • 如果types需要多个参数,则只能使用有界的types参数
  • 如果这个types需要一个下限,你别无select,只能使用有界通配符
  • “生产者”有上限,“消费者”有下限
  • 不要在返回types中使用通配符

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

但是,如果你做了这样的事情:

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

或者像polygenlubricants说的,如果你想匹配列表中的types参数与另一个types参数:

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

在第一个例子中,你得到了更多的types安全,然后返回Shape,因为你可以将结果传递给一个可能带有Shape子元素的函数。 例如,您可以将List<Square>传递给我的方法,然后将生成的Square传递给仅占用Square的方法。 如果你使用'?' 你将不得不将所得到的形状转换为Square,这不会是types安全的。

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

请考虑以下来自James Gosling第四版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 } 

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

“一般规则是尽可能使用通配符,因为带有通配符的代码通常比具有多个types参数的代码更具可读性。当决定是否需要typesvariables时,问问自己是否使用了typesvariables来关联两个或多个参数,或者将参数types与返回types联系起来,如果答案是否定的,那么通配符就足够了。

注意:在书中只给出了第二种方法,types参数名称是S而不是“T”。 书中没有第一种方法。

第二种方法有点冗长,但它允许你在里面引用T

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

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