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