2

I have these simple classes:

public abstract class Shape {
    public abstract void draw(Canvas c);
}

public class Circle extends Shape {
    private int x, y, radius;
    public void draw(Canvas c) { ... }
}

public class Rectangle extends Shape {
    private int x, y, width, height;
    public void draw(Canvas c) { ... }
}

These classes can be drawn on a canvas:

public class Canvas {
    public void draw(Shape s) {
        s.draw(this);
    }
}

I would like to put all Circle and Rectangle objects in a single List and draw all of them in a single iteration statement, like this:

public void drawAll(List<? extends Shape> shapes) {
    for (Shape s : shapes) {
        s.draw(this);
    }
}

Everything compiles.

The problem begins if I make a test class and try to create a list like this:

public class Main {

    public static void main(String[] args) {

        Canvas canvas = new Canvas();

        List<? extends Shape> list = new ArrayList<>();
        Circle circle = new Circle();
        Rectangle rectangle = new Rectangle();
        list.add(circle); // The method add(capture#1-of ? extends Shape) in the type List<capture#1-of ? extends Shape> is not applicable for the arguments (Circle)
        canvas.drawAll(list);
    }
}

As is documented in the code, I cannot add Circle and Rectangle objects to the List.

Question: how should I modify the code such, that I can construct a

List<? extends Shape> 

and next iterate over that List to draw the shapes.

thanks in advance.

Post Edit: Thanks! That was quite a breakthrough. Should have given the credits to SpaceTrucker and his crucial PECS link, but that wasn't possible. For some reason I had told myself that I should use extends and never questioned that assumption since.

jhulst
  • 353
  • 1
  • 3
  • 18
  • 1
    related: http://stackoverflow.com/questions/2723397/what-is-pecs-producer-extends-consumer-super – SpaceTrucker Apr 27 '17 at 10:42
  • It is basically the same as this from the official tutorial: https://docs.oracle.com/javase/tutorial/java/generics/wildcardGuidelines.html – jhulst Apr 27 '17 at 11:58

3 Answers3

2

Try to specify List type

List<Shape> list = new ArrayList<Shape>();
Shape circle = new Circle();
Rectangle rectangle = new Rectangle();
list.add(circle);
list.add(rectangle);
canvas.drawAll(list);
Sergey
  • 176
  • 2
  • 12
0

Since both circle and rectangle extend from Shape, you can do a List and add both objects.

List<Shape> list = new ArrayList<>();
Circle circle = new Circle();
Rectangle rectangle = new Rectangle();
list.add(circle);
list.add(rectangle);

Doing this, you can use all your Shape's methods. If you want to know if an object is Circle or Rectangle in order to do a non-shape method, you can do:

for(Shape shape : list){
    if(shape instanceof Circle){
        //do stuff casting object : ((Circle)shape).method()
    }
    else if(shape instanceof Rectangle){
        //do stuff casting object : ((Rectangle)shape).method()
    }

}
Aldeguer
  • 821
  • 9
  • 32
  • 1
    That instanceof usage is just ugly. I wouldn't recommend it, since people might think that it's somehow a good or acceptable way to code your logic. – Kayaman Apr 27 '17 at 10:55
  • Could you please explain me why is it that ugly and how could I do it in a better way? Thanks in advance – Aldeguer Apr 28 '17 at 07:38
  • 1
    It makes the code dependent on the actual classes, even though you're happily dealing with the `Shape` interface. Imagine you've written code like that in multiple places, and suddenly a `Triangle` shape is added. You have to go to every place and add an `instanceof` check for the new class too. If you intend to keep different classes in the same collection, you should be able to treat them through the interface. If not, then you have a code smell and maybe they shouldn't be in the same collection in the first place. – Kayaman Apr 28 '17 at 07:45
0

If you just want a list of shapes, you don't need any wildcards.

List<Shape> foo = new ArrayList<>();
foo.add(new Circle());
foo.add(new Rectangle());

works just fine.

If you were to use List<? extends Shape> it would mean that the list is either a List<Circle> or List<Rectangle>, and you can't add anything there since the actual type isn't known. However the following would compile and work just fine:

List<? extends Shape> foo = null;
foo = new ArrayList<Circle>();
foo = new ArrayList<Rectangle>();
foo = new ArrayList<Shape>();

you don't know the actual type of foo, but you know that you can get a Shape out from there.

Read the link in the comment for the PECS principle and it should clear things up a bit.

Kayaman
  • 72,141
  • 5
  • 83
  • 121