我读过 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>
or 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);
}
那么你能举一个例子,说明这种简化的签名是不可接受的吗?
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");
}
当然,为超类及其子类重载方法并不是很聪明,但它让我们看看实际推断出的返回值类型(和以前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
约束目的的答案。