The following code gives me an error for the line l.add
List<? extends Number> l = new ArrayList<Integer>();
l.add(1);
and forces me to write it as l.add(1, null);
Why is it so?
The following code gives me an error for the line l.add
List<? extends Number> l = new ArrayList<Integer>();
l.add(1);
and forces me to write it as l.add(1, null);
Why is it so?
With a wildcard on the variable l
, the compiler doesn't know (or care) which subclass of Number
(or Number
itself) the type parameter really is. It could be an ArrayList<Double>
or an ArrayList<BigInteger>
. It can't guarantee the type safety of what's passed in to add
, and because of type erasure, the JVM can't catch a type mismatch either. So the compiler preserves type safety by disallowing such calls to add
unless the value is null
, which can be any type.
To get add
to compile, you must declare l
as:
List<? super Integer> l = new ArrayList<Integer>();
or you can remove the wildcard:
List<Integer> l = new ArrayList<Integer>();
It has to be:
List<? super Integer> l = new ArrayList<Integer>();
l.add(1);
Note the <? super Integer>
declaration. It's called an upper-bound wildcard.
What does it do?
It restricts the Runtime type of the elements of the ArrayList
to be one of the super classes of Integer
, e.g. Integer
, Number
or Object
, which means that you will be able to assign l
to:
new ArrayList<Integer>
new ArrayList<Number>
new ArrayList<Object>
In the three cases, the statement l.add(1)
is perfectly valid, so there's no compile-time error.
More info:
List<? extends Number>
is different from List<T extends Number>
. For List<T extends Number>
, the l.add(new Integer(1))
would work.
If you use ? extends Number then you can't refer to the type, but you can still use ((List<Integer>)list).add((int) s)
.
You can write:
List<? extends Number> l = new ArrayList<>();
((List<Integer>)l).add((int) 1);
instead.
Generics is all about "Type Safety and invariance".
There are 2 scenario associated with your question.
Your first statement is about holding the reference.
List<? extends Number> l = new ArrayList<Integer>();
Here, compiler ensures that l
can hold the reference of Type List of objects which extends Number.
eg: List<Integer>
, List<BigInteger>
, List<Double>
etc.
In our case it is List<Integer>
. So far so good.
Now what am I allowed to add to l
? Users could want to add anything (which extends Number
, eg. Double
, BigInteger
or Integer
). This would break the type safety. So to overcome this, the compiler ensures that the user is not allowed to add to such kind of references.
This kind of references are used mainly when we are only iterating through a type of list.
eg:
// Here numbers can take reference of List type of objects which extends Number.
public void printValue(List<? extends Number> numbers){
// Iterate through all the numbers.
for(Number number: numbers){
System.out.println(number.intValue());
}
}
Now coming to the 2nd scenario of this discussion, that is modifying the list. For doing this, the compiler wants user to ensure type safety before making modification.
This can be done through <? super T>
.
List<? super Number> n = new ArrayList<Number>();
Here the compiler allows to add subtypes of Number to n
.
BigInteger b = new BigInteger("4");
Double d = new Double(2);
n.add(b); // valid
n.add(d); // valid
//Compiler won't allow here.
n.add(new CustomNumber()); // CustomNumber is not a subtype of Number. It addition in "n" is Invalid.
In simple words, List<? extends Number>
declares a List of elements of unspecified type. You only specify that the type of the elements extends the class Number. l
could be a List<Integer>
or a List<Double>
.
The compiler will allow to assign an element of the list to a Number, because it knows that anything in l
is a Number or a subclass thereof.
But it will not allow to add any element to the list because the variable l
could contain a List<Integer>
that accepts only Integers, or a List<Double>
that accepts only Doubles. No type is guaranteed to be accepted by the list. In some sense, List<? extends Number>
is a read-only list.
If you need l
to be a List of Numbers, maybe you pass it to a procedure that requires a List<Number>
, then you must initialize it as an ArrayList<Number>()
.
But if you will always use Integers with that list, you should declare it as List<Integer>
and initialize it with ArrayList<Integer>
. Remember that Integer is a final class, it allows the compiler to optimize the code much more than with Numbers.