最近,我读了这篇文章: 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);
}
}
我应该使用哪种方式? 是通配符在这种情况下,有用吗?
这取决于你需要做什么。 您需要使用有限制类型参数,如果你想要做这样的事情:
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
),那么就通配符就足够了。
即使你不是指有限制类型参数再次,如果您有多个范围有限制类型参数仍然需要。 这里有一个从报价安格莉卡朗格的Java泛型常见问题解答
是什么束缚通配符和类型参数的区别约束?
通配符只能有一个结合的,而一种类型的参数可以有几个边界。 通配符可以具有较低的或上限,同时有作为下界类型参数没有这样的事情。
通配符界限和类型参数界限往往无所适从,因为他们都是所谓的边界,并有部分相似的语法。 [...]
语法 :
type parameter bound T extends Class & Interface1 & … & InterfaceN wildcard bound upper bound ? extends SuperType lower bound ? super SubType
通配符只能有一个结合,无论是较低或上限。 通配符界限的名单是不允许的。
A型参数,在constrast,能有几个界限,但作为一个下界的类型参数没有这样的事情。
从有效的Java第二版行情,第28项:使用界通配符增加API的灵活性 :
为了获得最大的灵活性,使用上代表生产者或消费者的输入参数通配符类型。 [...]佩奇代表生产者- extends
,消费者为super
[...]
不要使用通配符类型作为返回类型 。 而不是为你的用户提供额外的灵活性,这将迫使他们在客户端代码中使用通配符类型。 如果使用得当,通配符类型是几乎看不到的一类用户。 它们造成的方法来接受他们应该接受的参数并拒绝那些他们应该拒绝。 如果类的用户必须考虑通配符类型外,还有可能出错类的API。
运用PECS原则,我们现在可以回到我们的addIfPretty
例子,使其更加灵活通过编写如下:
public <T extends Shape> void addIfPretty(List<? super T> list, T shape) { … }
现在我们可以addIfPretty
,比如说,一个Circle
,一个List<Object>
这显然是类型安全的,但我们的原宣言是不够灵活,允许它。
相关问题
- Java泛型:什么是佩奇?
- 有人能解释什么呢
<? super T>
<? super T>
的意思是,当它应该被使用,这种结构应该如何与合作<T>
和<? extends T>
<? extends T>
摘要
- 不要使用有限制类型参数/通配符,它们会增加你的API的灵活性
- 如果类型需要几个参数,你没有选择,只能使用有限的类型参数
- 如果类型需要一个下界,你没有选择,只能使用绑定的通配符
- “生产者”有upperbounds,“消费者”有lowerbounds
- 不要在返回类型使用通配符
在你的榜样,你并不真的需要用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);
}
}
在第一个例子中,你得到那么多一点的类型安全返回刚才的形状,因为你可以然后将结果传递给可能初具规模的子功能。 例如,你可以通过一个List<Square>
我的方法,然后通过将得到的广场到仅需要平方的方法。 如果使用“?” 你将不得不投产生的形状,来坊这不会是类型安全的。
在第二个例子中,你确保两个列表具有相同类型的参数(你不能做“?”,因为每一个“?”是不同的),这样就可以创建一个包含两者的所有元素的列表。
考虑下面的例子由詹姆斯·高斯林第4版下面我们要合并2 SinglyLinkQueue Java编程:
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) {
...
}
这是唯一的区别,因为据我所知。