Given:
import java.util.*;
public class Hancock {
//insert code here
list.add("foo");
}
}
Which two code fragments, inserted independently at line 5, will compile without warnings? (Choose two)
A. public void addString(List list) {
B. public void addString(List<String> list) {
C. public void addString(List<? super String> list) {
D. public void addString(List<? extends String> list) {
Correct answers are B & C.
Answers A and B are quite clear for me. For the answers C & D i know which way the inheritence is going, however i cannot understand why answer D does not compile in Eclipse while all others do (A with warrning about generic, B & C without warrings).
Error in Eclipse for answer D is The method add(capture#1-of ? extends String) in the type List<capture#1-of ? extends String> is not applicable for the arguments (String)
.
On the other hand this compiles:
public void addString() {
List<? extends String> list1 = new ArrayList<String>();
List<? super String> list2 = new ArrayList<String>();
}
Why? Why <? super String>
does not compile in method declaration while it does compile in variable declaration.
I know that String
is final class and cannot be extended by any other class but that does not explain to me what is going on here.
First, let's see answer C:
public void addString(List<? super String> list) {
list.add("foo");
}
This method declaration says that you will be allowed to pass List
objects which are parametrized by some super class of String
, for example String
or Object
. So:
- If you pass
List<String>
the list.add("foo")
will be perfectly valid.
- If you pass
List<Object>
the list.add("foo")
will be perfectly valid, because "foo" is a String
(and you can add a String
to a List<Object>
).
This means that answer C is correct.
Lets now see answer D.
If you have a method declaration like this:
public void addString(List<? extends String> list) {
}
this means that you will be able to pass List
objects parametrized by some unknown subtype of String
. So, when you do list.add("foo");
the compiler won't be aware if the provided object has a type that matches the unknown subtype of String
and therefore raises a compile-time error.
When you have:
public void addString() {
List<? extends String> list1 = new ArrayList<String>();
List<? super String> list2 = new ArrayList<String>();
}
This fragment compiles fine, because list1
is defined to hold List
objects that are of some unknown subtype of String
, including the String
itself, which is why it's valid.
The problem is that you won't be able to add anything, except null
.
As for list2
, the variable can hold List
objects which are parametrized by some super-type of String
, including the String
itself.
More info:
- What is PESC?
- What is the difference between super and extends in Java wildcards?
Firstly, generics don't care that String
is final. They work the same way for final and non-final classes.
With that in mind, it should be apparent why D is not allowed - if it was, you could do this:
void test() {
List<Integer> integers = new ArrayList<Integer>();
addADouble(integers);
int a = integers.get(0); // ????
}
void addADouble(List<? extends Number> list) {
list.add(new Double(5.0));
}
List<? extends Number>
is a "List of something that extends Number, but you don't know exactly what it's a List of." - it might be a List<Double>
, or a List<Integer>
, or a List<Number>
, or a List<YourCustomSubclassOfNumber>
, so you can't add anything to it because you don't know if it's the right type.