Java generics super keyword

Viewed 43379

I went through these topics

However, I still seem to be kind of lost with super keyword:

  1. When we declare a collection like that:

    List<? super Number> list = null;
    list.add(new Integer(0)); // this compiles
    list.add(new Object()); // this doesn't compile
    

shouldn't it be the opposite - we have a list that contains some objects (of unknown type) which are parents of Number. So Object should fit (since it is the parent of Number), and Integer shouldn't. The opposite is the case for some reason.

  1. Provided we have the following code

    static void test(List<? super Number> param) {
      param.add(new Integer(2));
    }
    
    public static void main(String[] args) {
      List<String> sList = new ArrayList<String>();
      test(sList);            // will never compile, however...
    }
    

It is impossible to compile the above code (and my sanity suggests that this is the right behaviour), but the basic logic could prove the opposite:

String is Object, Object is superclass of Number. So String should work.

I know this is crazy but isn't this the reason why they didn't allow <S super T> constructs? If yes, then why <? super T> is allowed?

Could someone help me restore the missing part of this logic chain?

7 Answers

May I give a very simple Example.

public void add(List<? super Number> list) {
}

this will allow these calls

add(new LinkedList<Number>());

and everything above Number like

add(new LinkedList<Object>());

but nothing below the hierarchy so not

add(new LinkedList<Double>());

or

add(new LinkedList<Integer>());

So since its not clear for the program to know whether you give a List with Number or Object the compiler cannot allow you to add anything above Number to it.

For example a List would not accept an Object in spite of Object who would accept a Number. But since this is not clear the only valid input would be Number and its sub types.

There are two angles here: what you can put into a collection and what you can get from a collection, when bounded types are involved.


Let's look at the ? extends Number case first. When a collection with such bounds is defined, what we know is that : every element will have an upper bound as Number. We don't know the exact type (might be an Integer/Long/etc), but we do know, for sure, that its upper bound is Number.

So reading from such a collection gets us a Number. This is the only guaranteed type we can get from it.

Writing to such a collection is prohibited. But why? Didn't I say that while we read - we will always get a Number, so why prohibit writing to it? The situation is slightly more involved here:

 List<Integer> ints = ....;
 List<? extends Number> numbers = ints;
 numbers.add(12D); // add a double in here

If addition would have been allowed into numbers, you could have effectively added a Double in a List of Integers.


Now to your example:

 List<? super Number> list = null;
 list.add(new Integer(0));
 list.add(new Object());

We know about list that it contains a certain supertype of Number, for example Object.

Reading from such a list would get us a certain type X, where X would be a parent of Number. So what would that be? You can't really know. It could be a theoretical MyNumber extends Number, or much simpler: an Object. Since you can't know for sure, the only safe thing to read from that would be the super-type of everything - Object.

What is a bit weird may be :

List<? super String> list = ...;
String s = list.get(0); // fails, compiler does not care that String is final

Writing to it is slightly more complicated, but only slightly. Remember what we know is inside that list: it's a type that Number extends/implements (if it were an interface), so you can always assign a subtype (or Number itself) to that supertype.

             Some type X
                 / \
                  |
                Number
                 / \
                  |
    Some type Y that we an put in here
Related