The short answer ("why it is strange") is that certain Java short-hand notations make two pieces of code look very similar when they are really very different.
So it can seem like "if this works, this should also work", but it's not so because important differences in the two bits of code are obscured.
Let's look at a few pieces of code separately:
public interface ListFactory {
List<?> getList();
}
So someone's going to give us a way to request a List<?>
. We can use it like this:
List<?> list1 = myListFactory.getList();
But if we do
list1.add(new A());
the compiler can't prove this is legal, because it depends on whether we happened to get a ListFactory implementation that returns a List<A>
, or maybe a List<String>
.
What if we replace the above with
List<?> list1 = new SpecialList();
list1.add(new A());
This is a little closer to your original code; but to the compiler the same problem exists: when it evaluates list1.add(new A());
it doesn't look for clues in the history of assignments to list1. It only knows that the compile-time reference type of list1 is List<?>
, so if list1.add(new A());
was illegal before, it's still illegal here.
(At a glance this may feel like a shortcoming of the compiler, but I think it's not; it generally isn't practical or desirable for the compiler to try and know any more than the reference type directly tells it. If we'd wanted to refer to our object in a way that allows add(new A())
we'd use a different reference type - e.g. List<A> list2 = new SpecialList();
.)
The above implies that we have a SpecialList.java; let's take a look at it:
public class SpecialList extends ArrayList<A> {
public SpecialList() {
add(new A());
}
}
This might seem a little silly, but it's probably no surprise that there's nothing wrong with it as far as the compiler is concerned (as long as A is defined and java.util.ArrayList is imported).
Note that add(new A());
in this case is shorthand for this.add(new A());
. In the SpecialList() constructor, this
is a reference of type SpecialList - which is declared to extend ArrayList<A>
- and certainly we can add(new A())
to a subclass of ArrayList<A>
.
So now we have all the pieces to make something like your original code:
List<?> list1 = new ArrayList<A>() {
{
add(new A());
}
}
list1.add(new A());
Lines 3 and 6 here look very similar now because of the Java syntactic sugar we've applied. But line 3 is really like our SpecialList() example - the reference type through which add() is invoked is an anonymous subclass of ArrayList<A>
. Line 6, though, is pretty much what it appears to be - and so it fails for the same reason it did in the first couple examples.
Similar analysis will explain the other weird distinctions you're seeing.