【发布时间】:2013-06-22 20:54:19
【问题描述】:
我读过 Joshua Bloch 的精彩“Effective 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);
}
那么你能举个例子说明这种简化的签名是不可接受的吗?
附:为什么官方实现中有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 约束的目的提供答案。
【问题讨论】:
-
T extends Object & ...导致擦除后方法的返回类型为Object而不是Comparable。这对于保留方法签名是必要的,这样针对旧 API 编译的代码仍然可以运行。 -
非常整洁的笔记。谢谢!
-
不错的更新。感谢您提出一个很好的问题!
标签: java generics effective-java