Given this example from the generics tutorial.
List<String> list = new ArrayList<>();
list.add("A");
// The following statement should fail since addAll expects
// Collection<? extends String>
list.addAll(new ArrayList<>());
Why does the last line not compile, when it seems it should compile. The first line uses a very similar construct and compiles without a problem.
Please explain elaborately.
First of all: unless you're using Java 7 all of this will not work, because the diamond <>
has only been introduced in that Java version.
Also, this answer assumes that the reader understands the basics of generics. If you don't, then read the other parts of the tutorial and come back when you understand those.
The diamond is actually a shortcut for not having to repeat the generic type information when the compiler could find out the type on its own.
The most common use case is when a variable is defined in the same line it's initialized:
List<String> list = new ArrayList<>(); // is a shortcut for
List<String> list = new ArrayList<String>();
In this example the difference isn't major, but once you get to Map<String, ThreadLocal<Collection<Map<String,String>>>>
it'll be a major enhancement (note: I don't encourage actually using such constructs!).
The problem is that the rules only go that far. In the example above it's pretty obvious what type should be used and both the compiler and the developer agree.
On this line:
list.addAll(new ArrayList<>());
it seems to be obvious. At least the developer knows that the type should be String
.
However, looking at the definition of Collection.addAll()
we see the parameter type to be Collection<? extends E>
.
It means that addAll
accepts any collection that contains objects of any unknown type that extends the type of our list
. That's good because it means you can addAll
a List<Integer>
to a List<Number>
, but it makes our type inference trickier.
In fact it makes the type-inference not work within the rules currently laid out by the JLS. In some situations it could be argued that the rules could be extended to work, but the current rules imply don't do it.
The explanation from the Type Inference documentation seems to answer this question directly ( unless I'm missing something else ).
Java SE 7 and later support limited type inference for generic instance creation; you can only use type inference if the parameterized type of the constructor is obvious from the context. For example, the following example does not compile:
List<String> list = new ArrayList<>();
list.add("A");
// The following statement should fail since addAll expects
// Collection<? extends String>
list.addAll(new ArrayList<>());
Note that the diamond often works in method calls; however, for greater clarity, it is suggested that you use the diamond primarily to initialize a variable where it is declared.
In comparison, the following example compiles:
// The following statements compile:
List<? extends String> list2 = new ArrayList<>();
list.addAll(list2);
When compiling a method invocation, javac needs to know the type of the arguments first, before determining which method signature matches them. So the method parameter type isn't known before the argument type is known.
Maybe this can be improved; as of today, the type of the argument is independent of the context.