Generics bug or am I just stupid?

B

bernd_no_junk

Please consider the followig code:


import java.util.List;
import java.util.ArrayList;

class ListObject {}

public class Test {
public static void main(String args[]) {

List<? extends ListObject> list = new ArrayList<ListObject>();

list.remove(new ListObject()); // works
list.add(new ListObject()); // compile time error
}
}


Why do I get the compile time error? Specifically:

Test.java:14: cannot find symbol
symbol : method add(ListObject)
location: interface java.util.List<capture of ? extends ListObject>
list.add(new ListObject());
x

Also why does list.remove work then ?

Regards,

Bernd
 
Z

zoopy

Please consider the followig code:


import java.util.List;
import java.util.ArrayList;

class ListObject {}

public class Test {
public static void main(String args[]) {

List<? extends ListObject> list = new ArrayList<ListObject>();

list.remove(new ListObject()); // works
list.add(new ListObject()); // compile time error
}
}


Why do I get the compile time error? Specifically:

Test.java:14: cannot find symbol
symbol : method add(ListObject)
location: interface java.util.List<capture of ? extends ListObject>
list.add(new ListObject());
x

Also why does list.remove work then ?

Regards,

Bernd
It's not easy to explain (at least not for me, I have to read the
article below over again), but basically it is because you can't add
objects to a collection that was declared using wildcards.
The article <http://today.java.net/pub/a/today/2004/01/15/wildcards.html>
gives in the section titled "A Side Benefit of Wildcards: Collections
That Are Partially Immutable" (page 2) a similar example and
accompanying explanation.
 
C

Chris Smith

List<? extends ListObject> list = new ArrayList<ListObject>();

list.remove(new ListObject()); // works
list.add(new ListObject()); // compile time error
Why do I get the compile time error? Specifically:

Test.java:14: cannot find symbol
symbol : method add(ListObject)
location: interface java.util.List<capture of ? extends ListObject>
list.add(new ListObject());
x

Also why does list.remove work then ?

It's very subtle, so pay attention and feel free to ask for
clarification if something doesn't make sense. It's because of the
wildcard in the type declaration.

Essentially, you've asked for a list whose element type is something
that is or extends ListObject. Note that this is subtly different from
a List whose element type is ListObject. Specifically, if you have two
variables declared as:

List<? extends ListObject> list1;
List<ListObject> list2;

Then:

class MyListObject extends ListObject { }
list1 = new ArrayList<MyListObject>(); // is legal
list2 = new ArrayList<MyListObject>(); // is NOT legal

But:

list2.add(new MyListObject()); // perfectly legal

So both references could point to lists that hold instances of
MyListObject. However, list1 contains the unique ability to hold
references to lists that are created to ONLY hold elements of
MyListObject. Do you see the difference?

The rest follows from there. 'list1' might be holding a reference to an
ArrayList<MyListObject>, in which case it would actually be illegal to
add a new object of class ListObject to that list. So it doesn't let
you do it. On the contrary, 'list2' knows that it is pointing to list
of ListObject -- not a subclass -- and so it can be proven that you can
safely add an object of class ListObject to the list.

As a further implication, you also could not add a new MyListObject via
list1, because it is just as clueless that the object it's pointing to
can contain instances of MyListObject. In fact, the only expression you
could legally add to list1 would be the literal 'null', which works
because it has magical type properties in which it adopts every possible
reference type at the same time. However, if you wanted to be able to
add MyListObject instances to 'list1', you could alter its type a bit to
look like this:

List<? extends ListObject super MyListObject> list1;

That's a mouthful, but it says that list1 (a) points to a List, and (b)
the element type of that list is assignable to ListObject, and (c) that
MyListObject is assignable to the element type of that list. That is
sufficient to prove to the compiler that it's safe to add MyListObject
instances to list1 (or instances of any subclass of MyListObject), so it
will let you do so.

You could try to pull the same trick with your original code, and you'd
end up with:

List<? extends ListObject super ListObject> list;

But the only type that is BOTH assignable to ListObject and assignable
from ListObject is -- you guessed it -- ListObject itself. So that long
declaration is semantically equivalent to:

List<ListObject> list;

So you may as well write the simpler code.

One important thing to bring out of this is a realization that you can
very easily say:

List<ListObject> list = new ArrayList<ListObject>();
list.add(new MyListObject());

So there's probably not a need to use the wildcard in the first place.
The wildcard is only necessary if you want to be able to point to lists
of types that are different from the base element type.

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

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

Chris Smith

Tor said:
IIRC, that syntax is meant for method parameters and the like, not
field types. Just use List<ListObject>.

Once you've got your head around it, there are a number of other places
where it is appropriate. For example, if I had two methods like this:

public List<UniversityLibraryCard> getUniversityCards() { ... }
public List<PublicLibraryCard> getPublicCards() { ... }

Then it would be an error to write this:

List<LibraryCard> cardList;

if (isUniversity()) cardList = getUniversityCards();
else cardList = getPublicCards();

The reason is that once I've got a List<LibraryCard>, it is perfectly
legal to write:

cardList.add(new PublicLibraryCard("John Doe"));

And yet I might not have a list that can deal with public library cards.
So if I don't intend to add new cards, I can work around the problem by
writing:

List<? extends LibraryCard> cardList;

if (isUniversity()) cardList = getUniversityCards();
else cardList = getPublicCards();

Of course if I *do* want to add new library cards, then I've got a
problem, but it's actually a logical problem. I've intentionally
forgotten what kind of cards are supposed to be in the list in order to
be more general, then I want to add one? I just don't have the
information to do it, and I chose to give up that information. So
that's not a problem with the language mechanisms, but rather with my
code.

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

Chris Smith - Lead Software Developer/Technical Trainer
MindIQ Corporation
 

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

Forum statistics

Threads
473,769
Messages
2,569,581
Members
45,056
Latest member
GlycogenSupporthealth

Latest Threads

Top