问题
Does anyone know why the following code does not compile? Neither add() nor addAll() works as expected. Removing the \"? extends\" part makes everything work, but then I would not be able to add subclasses of Foo.
List<? extends Foo> list1 = new ArrayList<Foo>();
List<? extends Foo> list2 = new ArrayList<Foo>();
/* Won\'t compile */
list2.add( new Foo() ); //error 1
list1.addAll(list2); //error 2
error 1:
IntelliJ says:
add(capture<? extends Foo>) in List cannot be applied to add(Foo)
The compiler says:
cannot find symbol
symbol : method addAll(java.util.List<capture#692 of ? extends Foo>)
location: interface java.util.List<capture#128 of ? extends Foo>
error 2:
IntelliJ gives me
addAll(java.util.Collection<? extends capture<? extends Foo>>) in List cannot be applied to addAll(java.util.List<capture<? extends Foo>>)
Whereas the compiler just says
cannot find symbol
symbol : method addAll(java.util.List<capture#692 of ? extends Foo>)
location: interface java.util.List<capture#128 of ? extends Foo>
list1.addAll(list2);
回答1:
(I assume here that Bar
and Baz
are both subtypes of Foo
.)
List<? extends Foo>
means a list of elements of some type, which is a subtype of Foo, but we don't know which type. Examples of such lists would be a ArrayList<Foo>
, a LinkedList<Bar>
and a ArrayList<Baz>
.
As we don't know which subtype is the type parameter, we can't put Foo
objects into it, neither Bar
or Baz
objects. But we still know that the type parameter is a subtype of Foo
, so every element already in the list (and which we can get from the list) must be a Foo
object, so we can use Foo f = list.get(0);
and similar things.
Such a list can only be used for taking elements out of the list, not to adding elements at all (apart from null
, but I don't know if the compiler actually allows this).
A List<Foo>
on the other hand allows adding any object which is a Foo
object - and as Bar
and Baz
are subtypes of Foo
, all Bar
and Baz
objects are Foo
objects, so they can be added, too.
回答2:
Remember PECS: Producer Extends, Consumer Super.
Since you are trying to add items to list2, it is a consumer and cannot be declared as List<? extends Foo>
. But then you are using list2 as a producer also when you add it to list1. Therefore, list2 is both a producer and a consumer and must be a List<Foo>
.
list1, as a pure consumer, can be a List<? super Foo>
.
回答3:
They're errors. Lets modify your code, considering that Bar
and Baz
are two different types extending Foo
:
List<? extends Foo> list1 = new ArrayList<Bar>();
List<? extends Foo> list2 = new ArrayList<Baz>();
If list1.add(new Foo())
was allowed, you could add Foo instances in a collection containing Bar instances. This explains the first error.
If list1.addAll(list2)
was allowed, all instances of Baz in list2 would be added to list1, which contains only Bar instances. This explains the second error.
回答4:
Let me try to explain in what case you might need to use <? extend Classname>
.
So, lets say you have 2 classes:
class Grand {
private String name;
public Grand(String name) {
this.setName(name);
}
public Grand() {
}
public void setName(String name) {
this.name = name;
}
}
class Dad extends Grand {
public Dad(String name) {
this.setName(name);
}
public Dad() {
}
}
And lets say you have 2 collections, each contains some Grands and some Dads:
List<Dad> dads = new ArrayList<>();
dads.add(new Dad("Dad 1"));
dads.add(new Dad("Dad 2"));
dads.add(new Dad("Dad 3"));
List<Dad> grands = new ArrayList<>();
dads.add(new Dad("Grandpa 1"));
dads.add(new Dad("Grandpa 2"));
dads.add(new Dad("Grandpa 3"));
Now, lets asume that we want to have collection, which will contain Grand or Dad objects:
List<Grand> resultList;
resultList = dads; // Error - Incompatable types List<Grand> List<Dad>
resultList = grands;//Works fine
How we can avoid this? Simply use wildcard:
List<? extends Grand> resultList;
resultList = dads; // Works fine
resultList = grands;//Works fine
Notice, that you cant add new items in such (resultList) collection. For more information you can read about Wildcard and PECS conseption in Java
回答5:
Sorry, maybe I misunderstood your question, but suposing:
public class Bar extends Foo{ }
this code:
List<Foo> list2 = new ArrayList<Foo>()
list2.add( new Bar() );
do not generate any error for me.
So, removing the wild card allows adding subclasses of Foo.
来源:https://stackoverflow.com/questions/5407995/lists-with-wildcards-cause-generic-voodoo-error