可能重复: Java泛型中的“超级”和“扩展”有什么区别?
a)
List<? super Shape> shapeSuper = new ArrayList<Shape>();
shapeSuper.add(new Square()); //extends from SHAP
shapeSuper.add(new DoubleSquare()); //extends from SQ
shapeSuper.add(new TripleSquare()); //extends from DS
shapeSuper.add(new Rectangle()); //extends from SHAP
shapeSuper.add(new Circle()); //extends from SHAP
for (Object object : shapeSuper) { ... }当我只能添加形状及其导数时,为什么迭代必须是对象的呢?
b)
List<? super Shape> shapeSuper = new ArrayList<Object>();
shapeSuper.add(new Object()); //compilation error 为什么上面的行会产生编译错误?
发布于 2009-02-26 23:22:19
对于您的示例,可以像Dan和plain那样使用普通的List<Shape>;您不需要使用通配符问号语法,比如List<? super Shape>或List<? extends Shape>)。我想你的基本问题可能是,“我什么时候会使用问号样式声明之一?”( Julien引用的Get和Put原则是对这个问题的一个很好的回答,但我认为它没有多大意义,除非你在一个例子中看到它。)下面是关于何时使用通配符的Get和Put原则的扩展版本。
如果.使用<? extends T>。
Foo<T> readSource的方法。如果.使用<? super T>。
Foo<T> writeDest的方法。下面是一个具体示例的演练,说明通配符背后的思想。假设您正在编写一个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); // compiler error! dsquares is not List<Square>编译器出错,因为dsquare List<DoubleSquare>的类型与processSquare,List<Square>的第一个参数的类型不匹配。也许DoubleSquare是一个正方形,但是为了您的processSquare方法,您需要告诉编译器List<DoubleSquare>是一个List<Square>。<? extends Square> 使用通配符告诉编译器,您的方法可以接受Square.的任何子类的列表。
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> 使用通配符告诉编译器,给定的参数可以是任何超类 of 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;
}
}发布于 2009-02-26 15:38:21
要在保罗的回答上扩展,将shapeSuper声明为List<?SuperShape>,您是说它可以接受任何形状超类的对象。对象是形状的超类。这意味着每个列表元素的公共超类都是Object。
这就是为什么必须在for循环中使用对象类型的原因。就编译器而言,列表可能包含非形状的对象。
发布于 2009-02-26 17:06:02
第(2.4)节中的"Get和Put原则“是来自Java泛型和集合的真正的宝石。
Get和put原则:只从结构中获取值时使用扩展通配符,只将值放入结构时使用超级通配符,在获得和放置值时不要使用通配符。

同时,将一个类型声明为List<? super Shape> shapeSuper也是一种糟糕的形式,因为它限制了它的使用。一般来说,我唯一使用外卡的时候是方法签名:
public void foo(List<? super Shape> shapeSuper)https://stackoverflow.com/questions/591004
复制相似问题