tags:

views:

866

answers:

8

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?

B)

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

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

Why does the above line produce a compilation error?

A: 

First line should read as

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

Please edit your original post rather than adding a reply if you have new information.
sk
+3  A: 

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

for (Shape shape : shapeSuper)
Paul Tomblin
+8  A: 

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.

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.

Dan Dyer
+1 for actually explaining what's wrong. Add a discussion of "? super T" vs. "? extends T" and I'll upvote again. Oh wait...
Michael Myers
hehe mmyers. i like the explanation too., too bad we can't upvote a second time
Johannes Schaub - litb
+3  A: 

A)

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

B)

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

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

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

Remember, Generics are not covariant.

bruno conde
+2  A: 

(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

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.

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

TMN
The problem with (B) is because you can't add things to a generic container declared with a wildcard parameter.
Michael Myers
Actually, I was wrong. add(new Shape()) works fine. The problem is a little thornier than that.
Michael Myers
+5  A: 
Julien Chastang
This is also known as the PECS principle (Effective Java 2nd Ed.). "Producer Extends, Consumer Super". If the parameter you are accepting is a producer, use 'extends'.
Ben Lings
A: 

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

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.

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 Drawable instance into it - and the compiler will prevent you from doing this.

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:

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>());
}
A: 

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.

Use <? extends T> if...

  • 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.

Use <? super T> if...

  • 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.

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:

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:

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.

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>:

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

}
ThisIsTheDave
Looks interesting, but your post was waaay too long to read.
Daniel