Reputation: 143
I have the following methods
Rectangle updateRectangle(Rectangle rect);
Collection<Rectangle> updateRectangles(Collection<Rectangle> rects);
I have two classes: Square
and Rectangle
. Square
extends Rectangle
.
The following works and has the desired behavior I want:
Square updateSquare(Square s) {
return updateRectangle(s);
}
The following gives me a type-error
Collection<Square> updateSquare(Collection<Square> squares) {
return updateRectangles(squares);
}
Can someone clarify to me why a Square
is a Rectangle
, but a collection of Squares
is not a Collection of Rectangles
? Is there something I can do to cleanly get around this "incompatible types" error?
Exact error: java: incompatible types: java.util.Collection<Square> cannot be converted to java.util.Collection<Rectangle>
Upvotes: 0
Views: 202
Reputation: 103913
This is called covariance/invariance/contravariance.
A list of rectangles is not a list of squares or vice versa: Lists are invariant.
Plain jane data types.. yes, there all squares are also rectangles: These are covariant.
That's because.. they fundamentally are. Here, let's take a trip to imaginary land and, nestled between unicorns and rainbows, we act as if lists of Xs and lists of Ys are covariant on the data type, e.g. that a list of squares is also a list of rectangles:
List<Square> squares = new ArrayList<Square>();
squares.add(new Square(20));
List<Rectangle> rectangles = squares;
rectangles.add(new Rectangle(10, 30));
Square s = squares.get(1); // UHOH!!!!!
And as we retrieve our square from our list of squares and notice the object we so obtain does not, in fact, have equal sides, our imaginary world crashes and disappears in a poof of smoke and leprecaun tears.
Awwwwww!
So, what's going wrong here?
The add
(and add-likes such as addAll
) op is in fact contravariant. The relationship is reversed! If I add a square to a list, that's okay.. if it is a list of square or any supertype. You can add squares to a list-o-squares, or a list-o-rectangles, or a list-o-objects. This is in reverse from the get(int idx)
operation, which is covariant: I can assign the result of listOfSquares.get(0)
to Square
, or Rectangle
, or Object
.
In java, you pick your own variance. The above code snippet from unicorn land does not compile here in the real world. Specifically, the line List<Rectangle> rectangles = squares;
won't compile, because by default generics acts invariant, thus making a list of squares not compatible with a list of rectangles.
But you control it, you can change it. Here, let's introduce covariance:
List<Square> squares = new ArrayList<Square>();
squares.add(new Square(20));
List<? extends Rectangle> rectangles = squares;
rectangles.add(new Rectangle(10, 30));
Square s = squares.get(1); // UHOH!!!!!
We have now introduced covariance: The definition of the rectangles
object reads: "Covariant on rectangle". This is fine and now this line compiles and runs just fine.
But the error moves: Java knows that given that you demanded covariance, that all add
methods need to disappear, because those aren't compatible with the notion of covariance. And so it is: The line rectangles.add(new Rectangle(10, 30))
will not compile. In fact, for any List<? extends Whatever>
, the add method is not invokable (unless you pass null
, but that's a fluke that's mostly irrelevant to variance).
You can even opt into contravariance:
List<Square> squares = new ArrayList<Square>();
squares.add(new Square(20));
List<? super Rectangle> rectangles = squares;
rectangles.add(new Rectangle(10, 30));
Square s = squares.get(1); // UHOH!!!!!
And now we have a list of contravariant rectangles. As expected, the world is now flipped upside down: Add is fine in this world. But get() is an issue.
First of all, a list of squares is not assignable to a list of rectangles if we're in contravariant world, so that line won't compile. But a list of objects would be, so, this will compile:
List<Object> o = new ArrayList<Object>();
List<? super Rectangle> r = o; // this is fine!
r.add(new Rectangle(10, 20)); // so is this
Rectangle z = r.get(0); // but not this
now add works fine, but get doesn't. Which shows up not as 'you cant call get at all', but as: the type of the 'get' invocation is just java.lang.Object.
This makes sense: You can assign a list of rectangles or a list of objects to a List<? super Rectangle>
variable, because adding a rectangle to either of these is fine. When getting values out, best I can do for you is to guarantee you that no matter what, it's an object, so that's all you get.
Weirdly, you probably can't. It sounds like updateRectangles
needs to both 'read' items from the list as well as 'write' items to it.
If all you were going to do is read from the list and you want the guarantee that whatever falls out, it's at least a rectangle, that's covariance, so you tell the compiler you opt into that: void readRectangles(Collection<? extends Rectangle> collection) {}
and you can pass a list-o-squares to this method just fine (and if you try to add to the collection, javac won't let you).
If all you were going to do is write, then.. opt into contravariance: void addSquares(Collection<? super Square> collection) {}
and you can call .add(new Square())
on the collection just fine. You can pass your list-o-rectangles or your list-o-squares.
But you want both, so all you get is invariance. What you want isn't really possible, not without resorting to unsafe casts or other hackery.
If you describe exactly what updateRectangles
actually does, we can help. If you never call .add(), go with Collection<? extends Rectangle>
and you're good to go.
Upvotes: 2
Reputation: 1014
Altough Square is a subclass of a Rectangle, Collection of squares is not a subclass of Collection of Rectangle from Java's type system point of view. Java expects, that a collection passed to the method will hold instances of exactly Rectangle type. To tell the compiled that a subclasses will also do, you have to use what it's called a bounded wildcard, like this:
Collection<? extends Rectangle> updateRectangles(Collection<? extends Rectangle> rects)
For more details on generics wildcards see the docs
Upvotes: 2