泛 型。。?超级 T

2022-09-04 05:00:14

可能的重复:
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) { ... }

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

B)

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

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

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


答案 1

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

在以下情况下使用...<? extends T>

  • 方法具有泛型类参数 readSourceFoo<T>
  • 方法从 readSource 获取 T 的实例,并且不关心检索到的实际对象是否属于 T 的子类。

在以下情况下使用...<? super T>

  • 方法具有泛型类参数 writeDestFoo<T>
  • 该方法将 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 的类型与处理 Square 的第一个参数的类型不匹配。也许 DoubleSquare 是 Square,但你需要告诉编译器,对于你的 processSquare 方法来说,a is-a。使用<? 扩展 Square>通配符告诉编译器您的方法可以获取 Square 的任何子类的列表。List<DoubleSquare>List<Square>List<DoubleSquare>List<Square>

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

接下来,您将改进应用程序以处理圆和正方形。您希望将所有已处理的形状聚合到一个包含圆形和正方形的列表中,因此您将已处理列表的类型从 a 更改为 :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>

编译器失败并显示新错误。现在,已处理列表的类型与处理平方的第二个参数不匹配。使用<? super Square>通配符告诉编译器给定的参数可以是 Square 的任何超类的列表。List<Shape>List<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; 
  }

}

答案 2

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

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


推荐