Search code examples
javagenericscollectionswildcardtype-erasure

Why cannot I modify collection through up bounded reference but can through its iterator?


    List<? extends Number> list1 = new ArrayList<Number>(){
        {addAll(Arrays.asList(1,2,3,4,5));}
    };
    ListIterator  listIterator = list1.listIterator();
    listIterator.next();
    listIterator.set(999);
    System.out.println(list1);

this code works and outs

[999, 2, 3, 4, 5]

But if I write so:

        List<? extends Number> list1 = new ArrayList<Number>(){
            {addAll(Arrays.asList(1,2,3,4,5));}
        };
        list1.set(0,999);

I see

java: method set in interface java.util.List<E> cannot be applied to given types;
  required: int,capture#1 of ? extends java.lang.Number
  found: int,int
  reason: actual argument int cannot be converted to capture#1 of ? extends java.lang.Number by method invocation conversion

Plese clarify this behaviour.

P.S. This question was arisen after watching code from

Collections.reverse method

public static void reverse(List<?> list) {
        int size = list.size();
        if (size < REVERSE_THRESHOLD || list instanceof RandomAccess) {
            for (int i=0, mid=size>>1, j=size-1; i<mid; i++, j--)
                swap(list, i, j);
        } else {
            ListIterator fwd = list.listIterator();
            ListIterator rev = list.listIterator(size);
            for (int i=0, mid=list.size()>>1; i<mid; i++) {
                Object tmp = fwd.next();
                fwd.set(rev.previous());
                rev.set(tmp);
            }
        }
    }

Solution

  • If your code example is complete, it is because you have dropped the generic information from your ListIterator definition. If you were to change it to include the generic info it would return a similar compilation error:

    List<? extends Number> list1 = new ArrayList<Number>(){
        {addAll(Arrays.asList(1,2,3,4,5));}
    };
    ListIterator<? extends Number>  listIterator = list1.listIterator();
    listIterator.next();
    listIterator.set(999);
    System.out.println(list1);
    

    Similarly, if you stripped the generic definition from your list you could add item directly without issue:

        List<? extends Number> list1 = new ArrayList<Number>(){
            {addAll(Arrays.asList(1,2,3,4,5));}
        };
        List list2 = list1;
        list2.set(0,999);
    

    This all comes down the fact that generics are really just syntactical sugar that aid in compilation, but are "erased" at runtime.

    http://docs.oracle.com/javase/tutorial/java/generics/erasure.html