我读过Joshua Bloch的精彩“有效Java”。但书中的一个例子对我来说还不清楚。它来自关于泛型的章节,确切的项目是“项目28:使用有界通配符来增加API灵活性”。
在这个项目中,它展示了如何使用有界类型参数和有界通配符类型从集合中选择最大元素算法的最通用和防弹(在类型系统的角度来看)版本。
编写的静态方法的最终签名如下所示:
public static <T extends Comparable<? super T>> T max(List<? extends T> list)
它与标准库中的Collections#max
函数大致相同。
public static <T extends Object & Comparable<? super T>> T max(Collection<? extends T> coll)
我理解为什么我们在T extends Comparable<? super T>
类型约束中需要有界通配符,但它在参数的类型中是否真的有必要?在我看来,如果我们只留下List<T>
或Collection<T>
,它会是一样的,不是吗?我的意思是这样的:
public static <T extends Comparable<? super T>> T wrongMin(Collection<T> 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<Point> {
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 != p.x ? x - p.x : y - p.y;
}
}
public static <T extends Comparable<? super T>> T min(Collection<? extends T> xs) {
Iterator<? extends T> 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<? super T>> T wrongMin(Collection<T> xs) {
return min(xs);
}
public static void main(String[] args) {
List<ColoredPoint> 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);
}
那么你可以建议一个这样的简化签名是不可接受的例子吗?
P.S。为什么在官方实施中有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");
}
当然,对于超类及其子类重载方法并不是很聪明,但它让我们看看实际推断出什么类型的返回值(points
和List<ColoredPoint>
一样)。
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.<type>method
告诉编译器应该推断出哪种类型:
expectsPointOrColoredPoint(Algorithms.<Point>min(points)); // print "Overloaded for Point"
expectsPointOrColoredPoint(Algorithms.<Point>wrongMin(points)); // will not compile
啊哈!这是答案。 List<ColoredPoint>
无法传递给期望Collection<Point>
的函数,因为泛型不是协变的(与数组不同),但可以传递给期望Collection<? extends Point>
的函数。
在这种情况下,我不确定在哪里或谁更喜欢使用显式类型参数,但至少它显示了wrongMin
可能不合适的地方。
感谢@erickson和@ tom-hawtin-tackline提供有关T extends Object
约束目的的答案。
答案 0 :(得分:5)
不同之处在于返回的类型,特别是受推理的影响,其中类型可以是在
class Top {
}
class Middle extends Top implements Comparable<Top> {
@Override
public int compareTo(Top o) {
//
}
}
class Bottom extends Middle {
}
使用您提供的签名:
public static <T extends Comparable<? super T>> T max(List<? extends T> list)
我们可以对此进行编码而不会出现错误,警告或(重要的)演员表:
List<Bottom> list;
Middle max = max(list); // T inferred to be Middle
如果您需要 Middle
结果而无需推理,则可以明确键入对Middle
的调用:
Comparable<Top> max = MyClass.<Middle>max(list); // No cast
或传递给接受Middle
的方法(推理不起作用)
someGenericMethodThatExpectsGenericBoundedToMiddle(MyClass.<Middle>max(list));
我不知道这是否有帮助,但为了说明编译器允许/推断的类型,签名看起来像这样(当然不是这个编译):
public static <Middle extends Comparable<Top>> Middle max(List<Bottom> list)
答案 1 :(得分:2)
之间的区别
T max(Collection<? extends T> coll)
和
T wrongMax(Collection<T> 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.<Point>min
中指定类型参数来修复第一个调用,而修复第二个调用则需要显式强制转换。
答案 2 :(得分:1)
不是一件容易的事,但我会尝试尽可能具体:
T max(Collection<? extends T> coll)
中的
你可以传递像List<Animal> or List<Cat> or List<Dog>
这样的论点,
并在T wrongMax(Collection<T> xs)
其中T是Animal,你不能作为参数通过
List<Dog>, List<Cat>
当然在运行时,您可以在List<Animal>
中添加Cat或Dog对象,但在编译时,您将无法在作为传递的列表类型中传递Animal的子类另一方面,wrongMax
方法中的参数可以使用max
方法。抱歉我的英语,我还在学习:),问候。