java 泛型..?超级T

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/591004/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-29 12:59:45  来源:igfitidea点击:

Generics..? Super T

javagenerics

提问by Julien Chastang

Possible Duplicate:
what is the difference between ‘super' and ‘extends' in Java Generics

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

Why must the iteration be of Objects when I can add only Shape and its derivatives?

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

B)

乙)

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

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

Why does the above line produce a compilation error?

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

回答by ThisIsTheDave

For your example, you can use a plain List<Shape>like Dan and Paul said; you don't need to use the wildcard question mark syntax such as List<? super Shape>or List<? extends Shape>). I think your underlying question may be, "when would I use one of the question mark style declarations?" (The Get and Put Principle that Julien cites is a great answer to this question, but I don't think it makes much sense unless you see it in the context of an example.) Here's my take at an expanded version of the Get and Put Principle for when to use wildcards.

对于您的示例,您可以使用List<Shape>Dan 和 Paul 所说的平原;您不需要使用通配符问号语法,例如List<? super Shape>List<? extends Shape>)。我认为您的潜在问题可能是,“我什么时候会使用问号样式声明之一?” (Julien 引用的 Get 和 Put 原则是对这个问题的一个很好的回答,但我认为它没有多大意义,除非你在一个例子的上下文中看到它。)这是我对 Get 和何时使用通配符的原则。

Use <? extends T>if...

使用<? extends T>如果...

  • A method has a generic class parameter Foo<T>readSource
  • The method GETS instances of T from readSource, and doesn't care if the actual object retrieved belongs to a subclass of T.
  • 一个方法有一个泛型类参数Foo<T>readSource
  • 该方法从 readSource 获取 T 的实例,并不关心检索到的实际对象是否属于 T 的子类。

Use <? super T>if...

使用<? super T>如果...

  • A method has a generic class parameter Foo<T>writeDest
  • The method PUTS instances of T into writeDest, and doesn't care if writeDest also contains objects that are subclasses of T.
  • 一个方法有一个泛型类参数Foo<T>writeDest
  • 该方法将 T 的实例放入 writeDest,并且不关心 writeDest 是否还包含作为 T 子类的对象。

Here's a walkthrough of a specific example that illustrates the thinking behind wildcards. Imagine you are writing a processSquare method that removes a square from a list, processes it, and stores the result in an output list. Here's a method signature:

下面是一个具体示例的演练,它说明了通配符背后的想法。假设您正在编写一个 processSquare 方法,该方法从列表中删除一个正方形,对其进行处理,并将结果存储在输出列表中。这是一个方法签名:

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

Now you create a list of DoubleSquares, which extend Square, and try to process them:

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

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

The compiler fails with an error because the type of dsquares List<DoubleSquare>doesn't match the type of the first parameter to processSquare, List<Square>. Perhaps a DoubleSquare is-a Square, but you need to tell the compiler that a List<DoubleSquare>is-a List<Square>for the purposes of your processSquare method. Use the <? extends Square>wildcard to tell the compiler that your method can take a List of any subclass of Square.

编译器失败并显示错误,因为 dsquaresList<DoubleSquare>的类型与 processSquare, 的第一个参数的类型不匹配List<Square>。也许一个 DoubleSquare is-a Square,但您需要告诉编译器一个List<DoubleSquare>is-aList<Square>用于 processSquare 方法。使用<? extends Square>通配符告诉编译器您的方法可以采用 Square 的任何子类的 List。

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

Next you improve the application to process Circles as well as Squares. You want to aggregate all your processed shapes in a single list that includes both circles and squares, so you changed the type of the processed list from a List<Square>to a List<Shape>:

接下来改进应用程序以处理圆形和方形。您想将所有处理过的形状聚合在一个包含圆形和正方形的列表中,因此您将处理过的列表的类型从 aList<Square>更改为 a List<Shape>

List<DoubleSquare> dsqares = ... 
List<Circle> circles = ... 
List<Shape> processed = new ArrayList<Square>;
processSquare(dsqares, processed); // compiler error! processed is not List<Square>

The compiler fails with a new error. Now the type of the processed list List<Shape>doesn't match the 2nd parameter to processSquare, List<Square>. Use the <? super Square>wildcard to tell the compiler that a given parameter can be a List of any superclassof Square.

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

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

Here's the full source code for the example. Sometimes I find it easier to learn stuff by starting with a working example and then breaking it to see how the compiler reacts.

这是示例的完整源代码。有时我发现通过从一个工作示例开始然后打破它以查看编译器如何反应来学习更容易。

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; 
  }

}

回答by Dan Dyer

To expand on Paul's answer, by declaring shapeSuper as List<? super Shape>, you are saying that it can accept any object that is a super class of Shape. Object is a superclass of shape. This means that the common superclass of each of the list's elements is Object.

为了扩大对保罗的回答,宣称shapeSuper为列表<?super Shape>,你是说它可以接受任何是Shape超类的对象。对象是形状的超类。这意味着每个列表元素的公共超类是 Object。

This is why you have to use the Object type in the for loop. As far as the compiler is concerned, the list might contain objects that are not Shapes.

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

回答by Julien Chastang

The "Get and Put Principle" in section (2.4) is a real gem from Java Generics and Collections:

第 (2.4) 节中的“获取和放置原则”是来自Java 泛型和集合的真正瑰宝:

The Get and Put Principle: use an extends wildcard when you only get values out of a structure, use super wildcard when you only put values into a structure, and don't use a wildcard when you both get and put.

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

alt text

替代文字

Also, declaring a type as List<? super Shape> shapeSuperis kind of bad form as it constrains its use. Generally, the only time I use wild cards is in method signatures:

此外,将类型声明List<? super Shape> shapeSuper为一种不好的形式,因为它限制了它的使用。一般来说,我唯一一次使用通配符是在方法签名中:

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

回答by Paul Tomblin

Try declaring shapeSuper as List<Shape>instead. Then you can do

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

for (Shape shape : shapeSuper)

回答by bruno conde

A)

一个)

Because superindicates the lower bounding class of a generic element. So, List<? super Shape>could represent List<Shape>or List<Object>.

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

B)

乙)

Because the compiler doesn't know what the actual type of List<? super Shape>is.

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

Your adding an object with shapeSuper.add(new Object());but the compiler only knows that the the generic type of Listis a super type of Shapebut doesn't know exactly witch one it is.

您添加了一个对象,shapeSuper.add(new Object());但编译器只知道泛型类型List是超类型,Shape但并不确切知道它是哪个类型。

In your example, List<? super Shape>could really be List<ShapeBase>forcing the compiler to disallow the shapeSuper.add(new Object());operation.

在您的示例中,List<? super Shape>确实可能会List<ShapeBase>强制编译器禁止该shapeSuper.add(new Object());操作。

Remember, Generics are not covariant.

请记住,泛型不是协变的

回答by TMN

(Disclaimer: I've never used "super" as a generic wildcard qualifier, so take this with a grain of salt...)

(免责声明:我从未将“超级”用作通用通配符限定符,因此请谨慎使用...)

For (A), actually you can't add Shape and its derivatives, you can only add Shape and its ancestors. I think maybe what you want is

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

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

Specifying "extends" means Shape and anything derived from Shape. Specifying "super" means Shape and anything Shape descended from.

指定“扩展”意味着 Shape 和从 Shape 派生的任何东西。指定“超级”意味着 Shape 和任何 Shape 的后代。

Not sure about (B), unless Object isn't implicit. What happens if you explicitly declare Shape as public class Shape extends Object?

不确定(B),除非 Object 不是隐式的。如果您显式声明 Shape 会发生什么public class Shape extends Object

回答by TMN

In reference to the above, I don't think this is correct:

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

by declaring shapeSuper as List<? super Shape> shapeSuper, you are saying that it can accept any object that is a super class of Shape

通过将 shapeSuper 声明为List<? super Shape> shapeSuper,您是说它可以接受任何属于 Shape 超类的对象

That does seem the intuitive at first glance, but actually I don't think that's how it works. You cannot insert just any superclass of Shape into shapeSuper. superShape is actually a reference to a List which may be restricted to holding a particular (but unspecified) supertype of Shape.

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

Lets imagine Shape implements Viewable and Drawable. So in this case the superShape reference may actually point to a List<Viewable>or a List<Drawable>(or indeed a List<Object>) - but we don't know which one. If it's actually a List<Viewable>you should not be able to insert a Drawableinstance into it - and the compiler will prevent you from doing this.

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

The lower bound construct is still actually very useful in making generic classes more flexible. In the following example it allows us to pass into the addShapeToSet method a Set defined to containing any superclass of Shape - and we can still insert a Shape into it:

下界构造实际上在使泛型类更加灵活方面仍然非常有用。在下面的示例中,它允许我们将定义为包含 Shape 的任何超类的 Set 传递给 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>());
}