Greetings Piotr,
> It seems natural that
> the list _implementations_ should impose restrictions on the types
> they contain, not the List interface. The same with Map. Am I
> missing something here?
A true subclass that satisfies the "is-a" relationship needs to adhere
to the superclass interface: by accepting any value the superclass
accepts. This is known as the Liskov substitution principle (another
way of putting this, A is a subtype of B then every instanceof B can
be replaced with A without changing the correctness of the program).
In other words, an implementation can only relax the parameter
requirements of the superclass, but not restrict it (every equals()
need to handle false, because Object.equals() is specified to accept
null).
Thus, if the List interface specifies to accept null values, so should
*all* the implementation. But that's not the case, unfortunately.
The Collection API doesn't adhere to the subtyping relationships, as
subclasses may throw an Exception optionally for any values (e.g.
handling nulls and read only values).
We are left with two options:
1. Annotate the bound of List as Nullable, and annotate the
collections that don't accept null with bound NonNull. This results
into the subclasses not type-checking properly, but allows the
clients/programmers to use List of nullables a bit easier
2. The otherway (NonNull for List, but Nullable for ArrayList). This
results into the subclasses type-checking properly, but would
inconvenient the clients that desire List of nullables.
We decided to go for the second option as it is more conservative and
safer. However, if we can show that the first option is still safe,
then we can change the current behavior. We may ignore the
unsoundness of the options interaction with raw types for the sake of
discussion.
Regards,
Mahmood