[英]Java: bounded wildcards or bounded type parameter?
最近,我读了这篇文章: 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);
}
}
我应该使用哪种方式? 通配符在这种情况下有用吗?
这取决于您需要做什么。 如果要执行以下操作,则需要使用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>
List<? extends Shape>
,您不能安全地add
到它(因为您可能具有List<Square>
和Circle
)。
因此,通过给有界类型参数命名,我们可以选择在通用方法的其他地方使用它。 当然,并非总是需要此信息,因此,如果您不需要太多了解类型(例如您的drawAll
),那么仅使用通配符就足够了。
即使您不再引用边界类型参数,如果您有多个边界,仍然需要边界类型参数。 这是Angelika Langer的Java泛型常见问题解答的报价
通配符绑定和类型参数绑定有什么区别?
通配符只能有一个界限,而类型参数可以有多个界限。 通配符可以有一个下限或上限,而类型参数没有下限。
通配符界限和类型参数界限经常被混淆,因为它们既称为界限,又具有部分相似的语法。 […]
语法 :
type parameter bound T extends Class & Interface1 & … & InterfaceN wildcard bound upper bound ? extends SuperType lower bound ? super SubType
通配符只能有一个界限,即下限或上限。 不允许使用通配符范围列表。
相反,类型参数可以有多个界限,但是没有类型参数的下界。
有效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>
。 这显然是类型安全的,但是我们最初的声明不够灵活,无法允许它。
<? super T>
<? super T>
是什么意思,什么时候应该使用,以及该构造应如何与<T>
和<? extends T>
<? extends T>
? 在您的示例中,您实际上不需要使用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子级的函数。 例如,您可以将List<Square>
传递给我的方法,然后将结果Square传递给仅采用Squares的方法。 如果您使用“?” 您将不得不将生成的Shape转换为Square,这是不安全的类型。
在第二个示例中,确保两个列表具有相同的类型参数(因为每个“?”不同,所以不能使用“?”),因此您可以创建一个包含两个元素的所有元素的列表。
考虑下面的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'。 书中没有第一种方法。
据我所知,在不需要类型参数的情况下,通配符允许使用更简洁的代码(例如,因为它在多个位置被引用,或者因为在其他答案中有详细说明,所以需要多个边界)。
在链接中,您表示我(在“通用方法”下)阅读了以下暗示该方向的陈述:
通用方法允许使用类型参数来表示方法的一个或多个参数的类型和/或其返回类型之间的依赖性。 如果没有这种依赖性,则不应使用通用方法。
[...]
使用通配符比声明显式类型参数更清晰,更简洁,因此应尽可能使用通配符。
[...]
通配符还具有可以在方法签名之外使用的优点,例如字段,局部变量和数组的类型。
第二种方法较为冗长,但是它允许您在其中引用T
:
for (T shape : shapes) {
...
}
据我了解,那是唯一的区别。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.