为什么我们需要有界wilcard

我读过Joshua Bloch的精彩“有效Java”。 但书中的一个例子对我来说还不清楚。 它来自关于generics的章节,具体项目是“第28项:使用有界通配符来增加API灵活性”

在这个项目中,它展示了如何使用有界类型参数和有界通配符类型编写从集合中选择最大元素的算法的最通用和防弹(在类型系统的角度)版本。

编写的静态方法的最终签名如下所示:

public static <T extends Comparable> T max(List list) 

它与标准库中的Collections#max函数大致相同。

 public static <T extends Object & Comparable> T max(Collection coll) 

我理解为什么我们需要有限的通配符在T extends Comparable T extends Comparable类型约束,但在参数的类型中是否真的有必要? 在我看来如果我们只留下ListCollection ,不是吗? 我的意思是这样的:

 public static <T extends Comparable> T wrongMin(Collection xs) 

我写了以下使用两个签名的愚蠢示例,并没有看到任何不同之处:

 public class Algorithms { public static class ColoredPoint extends Point { public final Color color; public ColoredPoint(int x, int y, Color color) { super(x, y); this.color = color; } @Override public String toString() { return String.format("ColoredPoint(x=%d, y=%d, color=%s)", x, y, color); } } public static class Point implements Comparable { public final int x, y; public Point(int x, int y) { this.x = x; this.y = y; } @Override public String toString() { return String.format("Point(x=%d, y=%d)", x, y); } @Override public int compareTo(Point p) { return x != px ? x - px : y - py; } } public static <T extends Comparable> T min(Collection xs) { Iterator iter = xs.iterator(); if (!iter.hasNext()) { throw new IllegalArgumentException("Collection is empty"); } T minElem = iter.next(); while (iter.hasNext()) { T elem = iter.next(); if (elem.compareTo(minElem) < 0) { minElem = elem; } } return minElem; } public static <T extends Comparable> T wrongMin(Collection xs) { return min(xs); } public static void main(String[] args) { List points = Arrays.asList( new ColoredPoint(1, 2, Color.BLACK), new ColoredPoint(0, 2, Color.BLUE), new ColoredPoint(0, -1, Color.RED) ); Point p1 = wrongMin(points); Point p2 = min(points); System.out.println("Minimum element is " + p1); } 

那么你可以建议一个这样简化的签名是不可接受的例子吗?

PS为什么在官方实施中有T extends Object

回答

好吧,多亏了@Bohemian,我已经设法弄清楚它们之间有什么区别。

考虑以下两种辅助方法

 private static void expectsPointOrColoredPoint(Point p) { System.out.println("Overloaded for Point"); } private static void expectsPointOrColoredPoint(ColoredPoint p) { System.out.println("Overloaded for ColoredPoint"); } 

当然,对于超类及其子类重载方法并不是很聪明,但它让我们看看实际推断出什么类型的返回值( pointsList和以前一样)。

 expectsPointOrColoredPoint(min(points)); // print "Overloaded for ColoredPoint" expectsPointOrColoredPoint(wrongMin(points)); // print "Overloaded for ColoredPoint" 

对于两种方法,推断类型为ColoredPoint

有时你想明确传递给重载函数的类型。 您可以通过以下几种方式完成此操作:

你可以施放:

 expectsPointOrColoredPoint((Point) min(points)); // print "Overloaded for Point" expectsPointOrColoredPoint((Point) wrongMin(points)); // print "Overloaded for Point" 

仍然没有区别……

或者你可以告诉编译器应该使用语法class.method推断出什么类型class.method

 expectsPointOrColoredPoint(Algorithms.min(points)); // print "Overloaded for Point" expectsPointOrColoredPoint(Algorithms.wrongMin(points)); // will not compile 

啊哈! 这是答案。 List无法传递给期望Collection函数,因为generics不是协变的(与数组不同),但可以传递给期望Collection函数Collection Collection

在这种情况下,我不确定在哪里或谁更喜欢使用显式类型参数,但至少它显示了wrongMin可能不合适的地方。

感谢@erickson和@ tom-hawtin-tackline关于T extends Object约束的目的的答案。

不同之处在于返回的类型,特别是受推理的影响,因此类型可以是Comparable类型和List类型之间的分层类型。 让我举个例子:

 class Top { } class Middle extends Top implements Comparable { @Override public int compareTo(Top o) { // } } class Bottom extends Middle { } 

使用您提供的签名:

 public static > T max(List list) 

我们可以编写这个没有错误,警告或(重要)演员:

 List list; Middle max = max(list); // T inferred to be Middle 

如果您需要 Middle结果而无需推理,则可以显式键入对Middle的调用:

  Comparable max = MyClass.max(list); // No cast 

或传递给接受Middle的方法(推理不起作用)

 someGenericMethodThatExpectsGenericBoundedToMiddle(MyClass.max(list)); 

我不知道这是否有帮助,但为了说明编译器允许/推断的类型,签名看起来像这样(当然不是这个编译):

 public static > Middle max(List list) 

和…之间的不同

 T max(Collection coll) 

 T wrongMax(Collection xs) 

是第二个版本的返回类型与集合的元素类型T完全相同,而在第一个版本中, T可以是元素类型的超类型。

第二个问题: T extends Object的原因是确保T是一个类而不是一个接口。


更新:稍微更“自然”的差异演示:假设您定义了以下两种方法:

 static void happy(ColoredPoint p, Point q) {} static void happy(Point p, ColoredPoint q) {} 

然后像这样打电话给第一个:

 happy(coloredPoint, min(points)); happy(coloredPoint, wrongMin(points)); 

类型推理引擎可以推断出在第一次调用中min的返回类型应该是Point并且代码将被编译。 第二次调用将无法编译,因为对happy的调用是模糊的。

不幸的是,类型推理引擎至少在Java 7中不够强大,所以实际上两个调用都无法编译。 区别在于可以通过在Algorithms.min指定类型参数来修复第一个调用Algorithms.min ,而修复第二个调用则需要显式强制转换。

不是一件容易的事,但我会尽量做到具体:

T max(Collection coll)您可以传递一个参数,如List or List or List ,并在T wrongMax(Collection xs) ,其中T是Animal你不能作为参数传递此List, List当然在运行时你可以在List添加Cat或Dog对象但是在编译时你将无法传递Animal的子类List的类型作为参数在wrongMax方法中传递,另一方面,在max方法中传递。 抱歉我的英语,我还在学习:),问候。