对于您的示例,您可以像 Dan 和 Paul 所说的那样使用普通的 List<Shape>;您不需要使用通配符问号语法,例如List<? super Shape> 或List<? extends Shape>)。我认为您的潜在问题可能是,“我什么时候使用问号样式声明之一?” (Julien 引用的 Get 和 Put 原则是对这个问题的一个很好的回答,但我认为除非您在示例的上下文中看到它,否则它没有多大意义。)这是我对 Get 和说明何时使用通配符的原则。
如果...则使用<? extends T>
- 一个方法有一个泛型类
参数
Foo<T>readSource
- 方法从 readSource 获取 T 的实例,并不关心检索到的实际对象是否属于 T 的子类。
如果...则使用<? super T>
- 一个方法有一个泛型类参数
Foo<T>writeDest
- 该方法将 T 的实例放入 writeDest,并且不关心 writeDest 是否还包含属于 T 子类的对象。
下面是一个具体示例的演练,说明了通配符背后的思想。假设您正在编写一个 processSquare 方法,该方法从列表中删除一个正方形,对其进行处理,并将结果存储在一个输出列表中。这是一个方法签名:
void processSquare(List<Square> iSqua, List<Square> oSqua)
{ Square s = iSqua.remove(0); s.doSquare(); oSqua.add(s); }
现在您创建一个扩展 Square 的 DoubleSquares 列表,并尝试处理它们:
List<DoubleSquare> dsqares = ...
List<Square> processed = new ArrayList<Square>;
processSquare(dsqares, processed); // compiler error! dsquares is not List<Square>
编译器因错误而失败,因为 dsquares List<DoubleSquare> 的类型与 processSquare 的第一个参数 List<Square> 的类型不匹配。也许 DoubleSquare is-a Square,但您需要告诉编译器 List<DoubleSquare> is-a List<Square> 以用于您的 processSquare 方法。 使用<? extends Square> 通配符告诉编译器您的方法可以采用Square 的任何子类的List。
void processSquare(List<? extends Square> iSqua, List<Square> oSqua)
接下来,您将改进应用程序以处理圆形和正方形。您希望将所有已处理的形状聚合到一个包含圆形和正方形的列表中,因此您将已处理列表的类型从 List<Square> 更改为 List<Shape>:
List<DoubleSquare> dsqares = ...
List<Circle> circles = ...
List<Shape> processed = new ArrayList<Square>;
processSquare(dsqares, processed); // compiler error! processed is not List<Square>
编译器失败并出现新错误。现在处理列表的类型List<Shape> 与processSquare 的第二个参数List<Square> 不匹配。 使用<? super Square> 通配符告诉编译器给定参数可以是Square 的任何超类 的列表。
void processSquare(List<? extends Square> iSqua,
List<? super Square> oSqua)
这是示例的完整源代码。有时我发现从一个工作示例开始,然后分解它以查看编译器如何反应,这样更容易学习东西。
package wild;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
public abstract class Main {
// In processing the square,
// I'll take for input any type of List that can PRODUCE (read) squares.
// I'll take for output any type of List that can ACCEPT (write) squares.
static void processSquare(List<? extends Square> iSqua, List<? super Square> oSqua)
{ Square s = iSqua.remove(0); s.doSquare(); oSqua.add(s); }
static void processCircle(List<? extends Circle> iCirc, List<? super Circle> oCirc)
{ Circle c = iCirc.remove(0); c.doCircle(); oCirc.add(c); }
public static void main(String[] args) {
// Load some inputs
List<Circle> circles = makeList(new Circle());
List<DoubleSquare> dsqares = makeList(new DoubleSquare());
// Collated storage for completed shapes
List<Shape> processed = new ArrayList<Shape>();
// Process the shapes
processSquare(dsqares, processed);
processCircle(circles, processed);
// Do post-processing
for (Shape s : processed)
s.shapeDone();
}
static class Shape { void shapeDone() { System.out.println("Done with shape."); } }
static class Square extends Shape { void doSquare() { System.out.println("Square!"); } }
static class DoubleSquare extends Square {}
static class Circle extends Shape { void doCircle() { System.out.println("Circle!"); } }
static <T> List<T> makeList(T a) {
List<T> list = new LinkedList<T>(); list.add(a); return list;
}
}