4.1
有边界的通配符
考虑一个简单的画图程序,它可以画矩形、圆形等形状。在程序中,我们定义了如下的
类层次结构来表示这些形状:
public abstract class Shape {
public abstract void draw(Canvas c);
}
public class Circle extends Shape {
private int x, y, radius;
public void draw(Canvas c) { ... }
}
public class Rectangle extends Shape {
private int x, y, width, height;
public void draw(Canvas c) { ... }
}
这些类可以在画布(Canvas)上绘制出来:
public class Canvas {
public void draw(Shape s) {
s.draw(this);
}
}
典型的,任何画作都会包含一些形状,假定这些形状表示为一个列表,那么为了方便,
在 Canvas 类中有一个方法将这些形状全部绘制出来:
public void drawAll(List<Shape> shapes) {
for (Shape s: shapes) {
s.draw(this);
}
}
现在,类型规则告诉我们,drawAll()调用的时候,只能传入 Shape 类型的列表,而不能
传入其他的(例如 List<Circle>)列表。真不幸,因为这个方法所做的只是从列表里面读 Sha
pe 而已,所以它应该可以传入 List<Circle>。因此我们真正想要的方法,实际上是可以接受
“
任何
形状的列表”作为参数的:
public void drawAll(List<? extends Shape> shapes) { ... }
我们看到了一点小小的区别:我们把类型 List<Shape>替换成了 List<? extends Shape>。
这样 drawAll()方法就可以接受“Shape 的任何子类型的列表”了,因此如果我们需要,就可
以传入 List<Circle>。
List<? extends Shape>就是
有边界通配符(
bounded wildcard
)
。?表示未知类型,正如我
们前面看到的通配符。不过这一次,我们知道这个未知类型实际是 Shape 的一个子类型,我
们说 Shape 是通配符的
上边界
。
和通常获得灵活性时一样,使用通配符获得灵活性也是要付出代价的。这个代价就是,
在方法体里面向集合写入元素是非法的了。例如,下面的代码是非法的:
public void addRectangle(List<? extends Shape> shapes) {