5

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?

Luke Willis
  • 8,429
  • 4
  • 46
  • 79
user1079065
  • 2,085
  • 9
  • 30
  • 53

5 Answers5

13

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>();
rgettman
  • 176,041
  • 30
  • 275
  • 357
  • Hi thanks a lot but then on the right hand side I am saying that it is of type Integer... then does this mean that it ignores it? – user1079065 Jul 18 '14 at 22:08
  • 2
    The mnemonic acronym "PECS" helps remember which to use. See http://stackoverflow.com/questions/2723397/java-generics-what-is-pecs . – Andy Thomas Jul 18 '14 at 22:08
  • @user1079065 Yes, it essentially ignores it. It's just like if you did `Animal a = new Cat()`, the compiler ignores the fact that `a` is a cat and treats it as just an animal. In your example, the compiler ignores the fact that it's an `ArrayList` and treats it as just a `List extends Number>`. – yshavit Jul 18 '14 at 22:19
  • It's not so much “ignoring” the type but rather *unable* to determine it. Though in this trivial example it would obviously have been possible. – 5gon12eder Sep 12 '14 at 12:11
7

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:

Community
  • 1
  • 1
Konstantin Yovkov
  • 62,134
  • 8
  • 100
  • 147
2

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.

yshavit
  • 42,327
  • 7
  • 87
  • 124
Venkat Rangan
  • 385
  • 1
  • 2
  • 7
  • I don't think you can write List in that context. Declaring a generic type is done in a method or class signature. – Florian F Sep 12 '14 at 00:56
  • `List` will only work here, if you try to add an instance of the previously defined generic type `T`. Integer *might* be a `T` but we can't know that here. – blalasaadri Sep 12 '14 at 11:58
2

Generics is all about "Type Safety and invariance".

There are 2 scenario associated with your question.

  1. Holding reference type.
  2. Making modification to contents of that reference.

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.
blalasaadri
  • 5,990
  • 5
  • 38
  • 58
Sadique
  • 140
  • 8
1

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.

Florian F
  • 1,300
  • 1
  • 12
  • 28