最近,我读了这篇文章: 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);
}
}
我应该使用哪种方式?在这种情况下,通配符是否有用?
答案 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
,例如Circle
,List<Object>
。这显然是类型安全的,但我们的原始声明不够灵活,不允许它。
<? super T>
mean and when should it be used and how this construction should cooperate with <T>
and <? extends T>
? 答案 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) {
...
}
据我所知,这是唯一的区别。