How to polymorphize <? extends ..> type in Java Generics?

H

hiwa

Below is an excerpt from _Generics in the Java Programming Language_(aka Java
Generics Tutorial) by Gilad Bracha.

<quote>
There is, as usual, a price to be paid for the flexibility of using wildcards.
That price is that it is now illegal to write into shapes in the body of the method.
For instance, this is not allowed:

public void addRectangle(List<? extends Shape> shapes) {
shapes.add(0, new Rectangle()); // compile-time error!
}

You should be able to figure out why the code above is disallowed. The type of
the second parameter to shapes.add() is ? extends Shape - an unknown subtype of
Shape. Since we don t know what type it is, we don t know if it is a supertype
of Rectangle; it might or might not be such a supertype, so it isn t safe to pass
a Rectangle there.
</quote>

My question is: what should we do in order for the above List<? extends Shape>
type to be a mixin list of various(heterogeneous) shapes(circle, rectangle,
etc.)? In other words, we'd like to make the above shapes.add(0, new Rectangle())
call legal for the 1.5 compiler. How could we do that?

More lengthy excerpt:
http://homepage1.nifty.com/algafield/fromJavaGenericsTutorial.html
 
S

Stefan Schulz

My question is: what should we do in order for the above List<? extends
Shape> type to be a mixin list of various(heterogeneous) shapes(circle,
rectangle, etc.)? In other words, we'd like to make the above
shapes.add(0, new Rectangle()) call legal for the 1.5 compiler. How
could we do that?

Declare it as List<Shape>. Since all shapes _are_ shapes, a List<Shape>
may contain any subtype of Shape, like circles, rectangles and so on.
However, you'll only get the base type of this list.
 
C

Chris Smith

hiwa said:
My question is: what should we do in order for the above List<? extends Shape>
type to be a mixin list of various(heterogeneous) shapes(circle, rectangle,
etc.)? In other words, we'd like to make the above shapes.add(0, new Rectangle())
call legal for the 1.5 compiler. How could we do that?

A list to which any shape can be added is a List<? super Shape>.
A list from which any shape can be retrieved is a List<? extends Shape>.

It sounds like you want both: guarantee that the list only contains
shapes, but also allow it to contain *any* shape. In wildcards, that
would be a List<? extends Shape super Shape>. But since that leaves no
flexibility for the implementation class, you may as well declare the
non-wildcard type List<Shape>. By doing this, you lose the flexibility
of using wildcards -- for example, no one could call your method and
pass a List<Rectangle> as a parameter. That actually makes sense,
because you wouldn't be able to add arbitrary shapes to such a things as
a List<Rectangle>.

If you *only* want to add rectangles, then you might consider:

List<? extends Shape super Rectangle>

That last type allows List<Shape> or List<Rectangle>, but does not allow
List<Object> (from which you couldn't safely retrieve a Shape), nor does
it allow List<SpecificRectange> (where SpecificRectangle extends
Rectangle, because you couldn't safely add a Rectangle to it). When
using an object of type List<? extends Shape super Rectangle>, you would
only be allowed to add Rectangle objects, but you would need to remove
objects using the abstract Shape supertype, since you're not sure of the
class of *other* objects that were already in the List when you received
it.

This all makes sense eventually; trust me.

--
www.designacourse.com
The Easiest Way To Train Anyone... Anywhere.

Chris Smith - Lead Software Developer/Technical Trainer
MindIQ Corporation
 
H

hiwa

Stefan said:
Declare it as List<Shape>

Chris said:
you may as well declare the non-wildcard type List<Shape>

And, IBM tutorial _Introduction to generic types in JDK 5.0_
by Brian Goetz:
<quote>
It is also important to remember that the bounded type does not
need to be a strict subtype of the bound; it could be the bound
as well. In other words, for a Collection<? extends Number>, you
could assign a Collection<Number> (although Number is not a strict
subtype of Number) as well as a Collection<Integer>, Collection<Long>,
Collection<Float>, and so on.
</quote>

Despite the powerful spell by three gurus, the following code, which
certainly can make a different shapes mixin, won't compile. Should we
give up?
--------------------------------------------------------------------
import java.util.*;

public class GenericsTest{

public static void main(String[] args){
List<Shape> ar = new ArrayList<Shape>();

ar.add(new Circle());
ar.add(new Rectangle());
ar.add(new Circle());
ar.add(new Rectangle());

Canvas cv = new Canvas();
cv.drawAll(ar);
cv.addRectangle(ar);
}
}

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

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

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

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

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

public void addRectangle(List<? extends Shape> shapes) {
shapes.add(0, new Rectangle());
}
}
-----------------------------------------------------------
 
J

John C. Bollinger

hiwa said:
And, IBM tutorial _Introduction to generic types in JDK 5.0_
by Brian Goetz:
<quote>
It is also important to remember that the bounded type does not
need to be a strict subtype of the bound; it could be the bound
as well. In other words, for a Collection<? extends Number>, you
could assign a Collection<Number> (although Number is not a strict
subtype of Number) as well as a Collection<Integer>, Collection<Long>,
Collection<Float>, and so on.
</quote>

Despite the powerful spell by three gurus, the following code, which
certainly can make a different shapes mixin, won't compile. Should we
give up?

You missed the _key_ points that Chris gave you:

[Chris Smith wrote:]
A list to which any shape can be added is a List<? super Shape>.
A list from which any shape can be retrieved is a List<? extends Shape>.

Note especially the first of those, as it provides the answer. Changing
this method signature:
public void addRectangle(List<? extends Shape> shapes)

to this:

public void addRectangle(List<? super Shape> shapes)

fixes the problem.


If you want to be able to add objects to a collection with a wildcard
type parameter then the wildcard *must* have an upper bound (specified
via super). Furthermore, the upper bound must be the class of the
object to be added or a superclass. With no upper bound you can't add
anything.
 
C

Chris Uppal

Chris said:
List<? extends Shape super Rectangle>

I don't think that is actually allowed, is it ? At least the nearest thing to
a spec I have for J5 doesn't seem to permit you to set upper /and/ lower
bounds.

I'm not sure that it would make much sense anyway -- if you know you can add
Rectangles to the list, then why not just declare that you take a List<? super
Rectangle> and read Rectangles off it ? Callers couldn't pass anything that
wasn't compatible with List<? super Rectangle> anyway, so you gain nothing by
restricting what you declare you are going to read from it.

BTW, I think that a syntax like:

List<read Shape>
List<write Rectangle>

would have been /much/ clearer (although I suppose there might be the odd
program that makes use of the identifiers "read" or "write" already). The
wildcard concept seem to be an unwarranted leakage of type theory into
practise. A "design burp", in fact.

-- chris
 
C

Chris Smith

Chris Uppal said:
I don't think that is actually allowed, is it ? At least the nearest thing to
a spec I have for J5 doesn't seem to permit you to set upper /and/ lower
bounds.

Hmm, you appear to be correct.
I'm not sure that it would make much sense anyway -- if you know you can add
Rectangles to the list, then why not just declare that you take a List<? super
Rectangle> and read Rectangles off it ?

You can't do that because List<? super Rectangle> could in reality refer
to a List<Object>, which may not contain Shape instances at all. You
could cast the result from Object to Shape, but then you've lost type-
safety since the compiler wouldn't complain about passing List<Object>
to the method.

The need for this is rare, because you'd need to be reading from *and*
writing to a List, and with different types. For example, the following
is a method that can't be written safely in Java because of this
omission:

/**
* Adds the bounding rectangle of this list to the list. The
* bounding rectangle is defined as the smallest rectangle that
* contains all other shapes in the list.
*/
public void addBoundingRect(List<...> shapes);

Declaring the parameter as List<? extends Shape> would prevent you from
adding the Rectangle. Declaring the list as List<? super Rectangle> is
a little better, but it would result in a ClassCastException at runtime
if you passed a List<Object> that contains objects of class Integer or
JTextField, for example.

Another option is List<Shape>, but then the caller could not pass an
instance of List<Rectangle> without copying it to a new list of the
correct generic type. Similar problems exist with List<Rectangle>,
because a List<Shape>. Potentially, List<Shape2D> or some other
intermediate generic type could exist as well, and it would be excluded
by both forms.
BTW, I think that a syntax like:

List<read Shape>
List<write Rectangle>

would have been /much/ clearer (although I suppose there might be the odd
program that makes use of the identifiers "read" or "write" already).

I'm not sure I agree here. Aside from my suspicion that "read" and
"write" are probably used as identifiers in at least 50% of Java
programs (and commonly throughout the core API), I'm also not sure that
"read" and "write" are really a correct abstraction in cases of generics
that are *not* related to the Collections API. How much sense does it
make to declare a Class<read Comparable>, instead of, for example, a
Class<? extends Comparable>?

--
www.designacourse.com
The Easiest Way To Train Anyone... Anywhere.

Chris Smith - Lead Software Developer/Technical Trainer
MindIQ Corporation
 
C

Chris Uppal

Chris said:
You can't do that because List<? super Rectangle> could in reality refer
to a List<Object>, which may not contain Shape instances at all.

My mistake. Getting confused again...

I'm also not sure that
"read" and "write" are really a correct abstraction in cases of generics
that are *not* related to the Collections API.

Yes, "get" and "put", or "pass" and "receive", would be more general. More
abstract, though, which is not necessarily a Good Thing. In fact one way to
phrase my real point is that the wildcard concept is /too/ abstract -- at least
for a programming language like Java.

-- chris
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,743
Messages
2,569,478
Members
44,899
Latest member
RodneyMcAu

Latest Threads

Top