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) { ... }
据我所知,这是唯一的区别。