Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fancy generics capture collision

Tags:

java

generics

Please give me a hint as to what is going on here:

List<? extends Number> a = new ArrayList<Number>();
List<? extends Number> b = new ArrayList<Number>();

a.addAll(b); // ouch! compiler yells at me, see the block below:
/*
  incompatible types
  found   : java.util.List<capture#714 of ? extends java.lang.Number>
  required: java.util.List<java.lang.Number>
 */

This simple code does not compile. I vaguely remember something related to type captures, like those should be mostly used in interface specs, not the actual code, but I never got dumbfounded like that.

This of course might be fixed brute-forcefully, like that:

List<? extends Number> a = new ArrayList<Number>();
List<? extends Number> b = new ArrayList<Number>();

@SuppressWarnings({"unchecked"})
List<Number> aPlain = (List<Number>) a;
@SuppressWarnings({"unchecked"}) 
List<Number> bPlain = (List<Number>) b;

aPlain.addAll(bPlain); 

So, do I really have to either give up captures in the declaration (the capture came to me from an interface, so I'll have to change some API), or stick with type casts with suppression annotations (which generally suck and complicates code a bit)?

like image 254
Anton Kraievyi Avatar asked May 02 '11 13:05

Anton Kraievyi


4 Answers

The problem is that if you use List<? extends Number> you could actually do:

List<? extends Number> a = new ArrayList<Integer>();
List<? extends Number> b = new ArrayList<Double>();

a.addAll(b); //ouch, would add Doubles to an Integer list

The compiler can't tell from List<? extends Number> what the actual type parameter is and thus won't let you do the add operation.

You also shouldn't cast the lists to List<Number> if you get them as a parameter, since you could actually have a list of Integer objects and add Double objects to it.

In that case you better create a new List<Number> and add the objects from both lists:

List<Number> c = new ArrayList<Number>(a.size() + b.size());
c.addAll(a);
c.addAll(b);

Edit: in case you create both lists locally, you would not neet the ? wildcard anyway (since you'd always have List<Number>).

like image 60
Thomas Avatar answered Oct 14 '22 02:10

Thomas


You have essentially two lists of possibly different types. Because ? extends Number means a class which extends Number. So for list a it can be classA and for list b it can be for example classB. They are not compatible, they can be totally different.

like image 45
Petar Minchev Avatar answered Oct 14 '22 02:10

Petar Minchev


Don't use the ? wildcard. It means "Some specific type I don't know", and since you don't know the type, you can't add anything to the list. Change your code to use List<Number> and everything will work.

This is fast becoming the most frequently asked Java question, in a hundred variations...

like image 3
Michael Borgwardt Avatar answered Oct 14 '22 03:10

Michael Borgwardt


PECS (producer-extends, consumer-super)

  • You cannot put anything into a type declared with an EXTENDS wildcard except for the value null, which belongs to every reference type
  • You cannot get anything out from a type declared with an SUPER wildcard except for a value of type Object, which is a super type of every reference type
like image 3
Jarek Przygódzki Avatar answered Oct 14 '22 04:10

Jarek Przygódzki