对于你的例子,你可以像Dan和Paul说的那样使用一个普通的
List<Shape>
;你不需要使用通配符问号语法,比如
List<? super Shape>
或
List<? extends Shape>
。我认为你的根本问题可能是,“什么时候我会使用问号样式的声明?”(Julien引用的Get and Put Principle是这个问题的一个很好的答案,但我认为除非你在一个例子的背景下看到它,否则它没有太多意义。)以下是我对何时使用通配符的Get and Put Principle的扩展版本。
如果......一个方法有一个泛型类参数
Foo<T>
readSource
方法从readSource获取T的实例,并且不关心实际检索到的对象是否属于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); }
现在您需要创建一个DoubleSquares列表,它是Square的扩展,并尝试处理它们:
List<DoubleSquare> dsqares = ...
List<Square> processed = new ArrayList<Square>
processSquare(dsqares, processed)
编译器出现错误,因为dsquares的类型是List,而processSquare方法的第一个参数类型是List。也许DoubleSquare是Square的子类,但你需要告诉编译器List是List的子类,以便在processSquare方法中使用。使用通配符告诉编译器你的方法可以接受任何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)
编译器出现了一个新错误。现在,被处理的列表List<Shape>
的类型与processSquare的第2个参数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 {
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) {
List<Circle> circles = makeList(new Circle());
List<DoubleSquare> dsqares = makeList(new DoubleSquare());
List<Shape> processed = new ArrayList<Shape>();
processSquare(dsqares, processed);
processCircle(circles, processed);
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;
}
}