泛型..?超级T

时间:2009-02-26 15:25:15

标签: java generics

  

可能重复:
  what is the difference between ‘super’ and ‘extends’ in Java Generics

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) { ... }

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

B)

List<? super Shape> shapeSuper = new ArrayList<Object>();  

shapeSuper.add(new Object()); //compilation error  

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

7 个答案:

答案 0 :(得分:25)

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

使用<? extends T> if ...

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

使用<? super T> if ...

  • 方法具有泛型类参数Foo<T> writeDest
  • 方法PUTS将实例写入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); // compiler error! dsquares is not List<Square>

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

void processSquare(List<? extends Square> iSqua, List<Square> oSqua)

接下来,您将改进应用程序以处理Circles和Squares。您希望在包含圆圈和方块的单个列表中汇总所有已处理的形状,因此您将已处理列表的类型从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; 
  }

}

答案 1 :(得分:23)

要扩展Paul's answer,将shapeSuper声明为List&lt;?超级形状&gt;,你说它可以接受任何超级Shape的对象。对象是形状的超类。这意味着每个列表元素的公共超类是Object。

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

答案 2 :(得分:17)

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

  

获取和放置原则:使用   当你得到时,扩展通配符   结构中的值,使用超级   只将值放入时的通配符   一个结构,不要使用通配符   当你们都得到并放好。

alt text

此外,将类型声明为List<? super Shape> shapeSuper是一种糟糕的形式,因为它限制了它的使用。通常,我使用通配符的唯一时间是方法签名:

public void foo(List<? super Shape> shapeSuper)

答案 3 :(得分:4)

尝试将shapeSuper声明为List<Shape>。然后就可以了

for (Shape shape : shapeSuper)

答案 4 :(得分:3)

<强> A)

因为super表示通用元素的下限类。因此,List<? super Shape>可以代表List<Shape>List<Object>

<强> B)

因为编译器不知道List<? super Shape>的实际类型是什么。

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

在您的示例中,List<? super Shape>可能真的是List<ShapeBase>,迫使编译器禁止shapeSuper.add(new Object());操作。

请记住,Generics are not covariant

答案 5 :(得分:2)

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

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

List<? extends Shape> shapeSuper = new ArrayList<Shape>();

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

不确定(B),除非Object不是隐含的。如果您明确将Shape声明为public class Shape extends Object会怎样?

答案 6 :(得分:0)

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

  

通过将shapeSuper声明为List<? super Shape> shapeSuper,你就是这样说的   可以接受任何超级对象   形状类

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

让我们假设Shape实现了Viewable和Drawable。因此,在这种情况下,superShape引用实际上可能指向List<Viewable>List<Drawable>(或实际上是List<Object>) - 但我们不知道哪一个。如果它实际上是List<Viewable>,你就不应该在其中插入Drawable实例 - 编译器会阻止你这样做。

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

public void addShapeToSet(Set<? super Shape> set) {
    set.add(new Shape());
}

public void testAddToSet() {
    //these all work fine, because Shape implements all of these:
    addShapeToSet(new HashSet<Viewable>());
    addShapeToSet(new HashSet<Drawable>());           
    addShapeToSet(new HashSet<Shape>());           
    addShapeToSet(new HashSet<Object>());
}