generics..? 超级T

可能重复:
Java Generics中’super’和’extends’之间的区别是什么?

一个)

List shapeSuper = new ArrayList(); 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) { ... } 

当我只能添加Shape及其衍生物时,为什么迭代必须是Objects?

B)

 List shapeSuper = new ArrayList(); shapeSuper.add(new Object()); //compilation error 

为什么上面的行会产生编译错误?

对于你的例子,你可以像Dan一样使用普通的List和保罗说; 您不需要使用通配符问号语法,例如List ListList List )。 我认为你的基本问题可能是“我何时会使用其中一种问号样式声明?” (Julien引用的Get和Put原则是对这个问题的一个很好的答案,但我认为除非你在一个例子的上下文中看到它,否则它没有多大意义。)这是我对Get和扩展版本的看法。 Put原则何时使用通配符。

使用 如果……

  • 方法具有generics类参数Foo readSource
  • 方法GETS来自readSource的T实例,并不关心检索的实际对象是否属于T的子类。

使用 如果……

  • 方法具有generics类参数Foo writeDest
  • 方法PUTS将T实例写入writeDest,并不关心writeDest是否还包含作为T子类的对象。

这是一个特定示例的演练,演示了通配符背后的思想。 想象一下,您正在编写一个processSquare方法,该方法从列表中删除一个方块,对其进行处理,并将结果存储在输出列表中。 这是方法签名:

 void processSquare(List iSqua, List oSqua) { Square s = iSqua.remove(0); s.doSquare(); oSqua.add(s); } 

现在,您创建一个DoubleSquares列表,它扩展Square,并尝试处理它们:

 List dsqares = ... List processed = new ArrayList; processSquare(dsqares, processed); // compiler error! dsquares is not List 

编译器因错误而失败,因为dsquares List的类型与processSquare, List的第一个参数的类型不匹配。 也许DoubleSquare是一个Square,但是为了processSquare方法的目的,你需要告诉编译器List是一个List使用 wildcard告诉编译器您的方法可以获取Square的任何子类的List。

 void processSquare(List iSqua, List oSqua) 

接下来,您将改进应用程序以处理Circles和Squares。 您希望在包含圆和正方形的单个列表中聚合所有已处理的形状,因此您将已处理列表的类型从List更改为List

 List dsqares = ... List circles = ... List processed = new ArrayList; processSquare(dsqares, processed); // compiler error! processed is not List 

编译器失败并出现新错误。 现在,处理列表List的类型与processSquare, List的第二个参数不匹配。 使用 通配符告诉编译器一个给定的参数可以是任何超类 Square的List。

 void processSquare(List iSqua, List 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 iSqua, List oSqua) { Square s = iSqua.remove(0); s.doSquare(); oSqua.add(s); } static void processCircle(List iCirc, List oCirc) { Circle c = iCirc.remove(0); c.doCircle(); oCirc.add(c); } public static void main(String[] args) { // Load some inputs List circles = makeList(new Circle()); List dsqares = makeList(new DoubleSquare()); // Collated storage for completed shapes List processed = new ArrayList(); // 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  List makeList(T a) { List list = new LinkedList(); list.add(a); return list; } } 

通过将shapeSuper声明为List <?来扩展Paul的答案 。 super Shape>,你说它可以接受任何超级Shape的对象。 对象是形状的超类。 这意味着每个列表元素的公共超类是Object。

这就是你必须在for循环中使用Object类型的原因。 就编译器而言,列表可能包含不是形状的对象。

第(2.4)节中的“获取和放置原则”是来自Java Generics和Collections的真正的gem:

Get和Put原则:当你只从结构中获取值时使用扩展通配符,当你只将值放入结构时使用超级通配符,并且当你获取和放置时不使用通配符。

替代文字http://sofzh.miximages.com/java/0596527756_cat.gif

另外,将类型声明为List shapeSuper List shapeSuper是一种糟糕的forms,因为它限制了它的使用。 通常,我使用通配符的唯一方法是在方法签名中:

 public void foo(List shapeSuper) 

尝试将shapeSuper声明为List 。 那你可以做

 for (Shape shape : shapeSuper) 

一个)

因为super表示通用元素的下界类。 那么, List List可以表示ListList

B)

因为编译器不知道List的实际类型是List List是。

使用shapeSuper.add(new Object());添加对象shapeSuper.add(new Object()); 但是编译器只知道List的generics类型是Shape的超类型,但它并不完全知道它是什么。

在你的例子中, List List可能真的是List强制编译器禁止使用shapeSuper.add(new Object()); 操作。

请记住, generics不是协变的 。

(免责声明:我从来没有使用“超级”作为通用的通配符限定符,所以请带上一粒盐…)

对于(A),实际上你不能添加Shape及其衍生物,你只能添加Shape及其祖先。 我想也许你想要的是

 List shapeSuper = new ArrayList(); 

指定“extends”表示Shape和从Shape派生的任何内容。 指定“超级”意味着形状和任何形状的后代。

不确定(B),除非Object不是隐含的。 如果你明确地将Shape声明为public class Shape extends Object什么?

参考上文,我不认为这是正确的:

通过将shapeSuper声明为List shapeSuper List shapeSuper ,你说它可以接受任何超级Shape的对象

乍一看似乎很直观,但实际上我并不认为这是如何运作的。 您不能将任何超类Shape插入shapeSuper。 superShape实际上是对List的引用,List可能仅限于保存特定(但未指定)的超类型Shape。

让我们假设Shape实现了Viewable和Drawable。 所以在这种情况下,superShape引用实际上可能指向List Viewable ListList (或实际上是List ) – 但我们不知道哪一个。 如果它实际上是List那么你应该无法在其中插入Drawable实例 – 编译器将阻止你这样做。

下限构造实际上在使通用类更灵活方面仍然非常有用。 在下面的例子中,它允许我们将定义为包含任何超类Shape的Set传递给addShapeToSet方法 – 我们仍然可以在其中插入一个Shape:

 public void addShapeToSet(Set set) { set.add(new Shape()); } public void testAddToSet() { //these all work fine, because Shape implements all of these: addShapeToSet(new HashSet()); addShapeToSet(new HashSet()); addShapeToSet(new HashSet()); addShapeToSet(new HashSet()); }