How can I add to List<? extends Number> data structures?

前端 未结 6 962
暗喜
暗喜 2020-11-22 02:19

I have a List which is declared like this :

 List foo3 = new ArrayList();

I tried to add 3 to foo3.

相关标签:
6条回答
  • 2020-11-22 02:56

    Sorry, but you can't.

    The wildcard declaration of List<? extends Number> foo3 means that the variable foo3 can hold any value from a family of types (rather than any value of a specific type). It means that any of these are legal assignments:

    List<? extends Number> foo3 = new ArrayList<Number>;  // Number "extends" Number
    List<? extends Number> foo3 = new ArrayList<Integer>; // Integer extends Number
    List<? extends Number> foo3 = new ArrayList<Double>;  // Double extends Number
    

    So, given this, what type of object could you add to List foo3 that would be legal after any of the above possible ArrayList assignments:

    • You can't add an Integer because foo3 could be pointing at a List<Double>.
    • You can't add a Double because foo3 could be pointing at a List<Integer>.
    • You can't add a Number because foo3 could be pointing at a List<Integer>.

    You can't add any object to List<? extends T> because you can't guarantee what kind of List it is really pointing to, so you can't guarantee that the object is allowed in that List. The only "guarantee" is that you can only read from it and you'll get a T or subclass of T.

    The reverse logic applies to super, e.g. List<? super T>. These are legal:

    List<? super Number> foo3 = new ArrayList<Number>; // Number is a "super" of Number
    List<? super Number> foo3 = new ArrayList<Object>; // Object is a "super" of Number
    

    You can't read the specific type T (e.g. Number) from List<? super T> because you can't guarantee what kind of List it is really pointing to. The only "guarantee" you have is you are able to add a value of type T (or any subclass of T) without violating the integrity of the list being pointed to.


    The perfect example of this is the signature for Collections.copy():

    public static <T> void copy(List<? super T> dest,List<? extends T> src)
    

    Notice how the src list declaration uses extends to allow me to pass any List from a family of related List types and still guarantee it will produce values of type T or subclasses of T. But you cannot add to the src list.

    The dest list declaration uses super to allow me to pass any List from a family of related List types and still guarantee I can write a value of a specific type T to that list. But it cannot be guaranteed to read the values of specific type T if I read from the list.

    So now, thanks to generics wildcards, I can do any of these calls with that single method:

    // copy(dest, src)
    Collections.copy(new ArrayList<Number>(), new ArrayList<Number());
    Collections.copy(new ArrayList<Number>(), new ArrayList<Integer());
    Collections.copy(new ArrayList<Object>(), new ArrayList<Number>());
    Collections.copy(new ArrayList<Object>(), new ArrayList<Double());
    

    Consider this confusing and very wide code to exercise your brain. The commented out lines are illegal and the reason why is stated to the extreme right of the line (need to scroll to see some of them):

      List<Number> listNumber_ListNumber  = new ArrayList<Number>();
    //List<Number> listNumber_ListInteger = new ArrayList<Integer>();                    // error - can assign only exactly <Number>
    //List<Number> listNumber_ListDouble  = new ArrayList<Double>();                     // error - can assign only exactly <Number>
    
      List<? extends Number> listExtendsNumber_ListNumber  = new ArrayList<Number>();
      List<? extends Number> listExtendsNumber_ListInteger = new ArrayList<Integer>();
      List<? extends Number> listExtendsNumber_ListDouble  = new ArrayList<Double>();
    
      List<? super Number> listSuperNumber_ListNumber  = new ArrayList<Number>();
    //List<? super Number> listSuperNumber_ListInteger = new ArrayList<Integer>();      // error - Integer is not superclass of Number
    //List<? super Number> listSuperNumber_ListDouble  = new ArrayList<Double>();       // error - Double is not superclass of Number
    
    
    //List<Integer> listInteger_ListNumber  = new ArrayList<Number>();                  // error - can assign only exactly <Integer>
      List<Integer> listInteger_ListInteger = new ArrayList<Integer>();
    //List<Integer> listInteger_ListDouble  = new ArrayList<Double>();                  // error - can assign only exactly <Integer>
    
    //List<? extends Integer> listExtendsInteger_ListNumber  = new ArrayList<Number>(); // error - Number is not a subclass of Integer
      List<? extends Integer> listExtendsInteger_ListInteger = new ArrayList<Integer>();
    //List<? extends Integer> listExtendsInteger_ListDouble  = new ArrayList<Double>(); // error - Double is not a subclass of Integer
    
      List<? super Integer> listSuperInteger_ListNumber  = new ArrayList<Number>();
      List<? super Integer> listSuperInteger_ListInteger = new ArrayList<Integer>();
    //List<? super Integer> listSuperInteger_ListDouble  = new ArrayList<Double>();     // error - Double is not a superclass of Integer
    
    
      listNumber_ListNumber.add(3);             // ok - allowed to add Integer to exactly List<Number>
    
      // These next 3 are compile errors for the same reason:
      // You don't know what kind of List<T> is really
      // being referenced - it may not be able to hold an Integer.
      // You can't add anything (not Object, Number, Integer,
      // nor Double) to List<? extends Number>      
    //listExtendsNumber_ListNumber.add(3);     // error - can't add Integer to *possible* List<Double>, even though it is really List<Number>
    //listExtendsNumber_ListInteger.add(3);    // error - can't add Integer to *possible* List<Double>, even though it is really List<Integer>
    //listExtendsNumber_ListDouble.add(3);     // error - can't add Integer to *possible* List<Double>, especially since it is really List<Double>
    
      listSuperNumber_ListNumber.add(3);       // ok - allowed to add Integer to List<Number> or List<Object>
    
      listInteger_ListInteger.add(3);          // ok - allowed to add Integer to exactly List<Integer> (duh)
    
      // This fails for same reason above - you can't
      // guarantee what kind of List the var is really
      // pointing to
    //listExtendsInteger_ListInteger.add(3);   // error - can't add Integer to *possible* List<X> that is only allowed to hold X's
    
      listSuperInteger_ListNumber.add(3);      // ok - allowed to add Integer to List<Integer>, List<Number>, or List<Object>
      listSuperInteger_ListInteger.add(3);     // ok - allowed to add Integer to List<Integer>, List<Number>, or List<Object>
    
    0 讨论(0)
  • 2020-11-22 02:56

    You can fudge it by creating a reference to the List with a different type.

    (These are the "unsafe casts" mentioned by sepp2k.)

    List<? extends Number> list = new ArrayList<Integer>();
    
    // This will not compile
    //list.add(100);
    
    // WORKS, BUT NOT IDEAL
    List untypedList = (List)list;
    // It will let you add a number
    untypedList.add(200);
    // But it will also let you add a String!  BAD!
    untypedList.add("foo");
    
    // YOU PROBABLY WANT THIS
    // This is safer, because it will (partially) check the type of anything you add
    List<Number> superclassedList = (List<Number>)(List<?>)list;
    // It will let you add an integer
    superclassedList.add(200);
    // It won't let you add a String
    //superclassedList.add("foo");
    // But it will let you add a Float, which isn't really correct
    superclassedList.add(3.141);
    // ********************
    // So you are responsible for ensuring you only add/set Integers when you have
    // been given an ArrayList<Integer>
    // ********************
    
    // EVEN BETTER
    // If you can, if you know the type, then use List<Integer> instead of List<Number>
    List<Integer> trulyclassedList = (List<Integer>)(List<?>)list;
    // That will prevent you from adding a Float
    //trulyclassedList.add(3.141);
    
    System.out.println("list: " + list);
    

    Because untypedList, superclassedList and trulyclassedList are just references to list, you will still be adding elements to the original ArrayList.

    You don't actually need to use (List<?>) in the example above, but you might need it in your code, depending on the type of list you were given.

    Note that using ? will give you compiler warnings, until you put this above your function:

    @SuppressWarnings("unchecked")
    
    0 讨论(0)
  • 2020-11-22 02:57

    You could do this instead:

      List<Number> foo3 = new ArrayList<Number>();      
      foo3.add(3);
    
    0 讨论(0)
  • 2020-11-22 03:13

    You can't (without unsafe casts). You can only read from them.

    The problem is that you don't know what exactly the list is a list of. It could be a list of any subclass of Number, so when you try to put an element into it, you don't know that the element actually fits into the list.

    For example the List might be a list of Bytes, so it would be an error to put a Float into it.

    0 讨论(0)
  • 2020-11-22 03:18

    It has been confusing to me even though I read answers here, until I found the comment by Pavel Minaev:

    Note that List < ? extends Number > does not mean "list of objects of different types, all of which extend Number". It means "list of objects of a single type which extends Number"

    After this I was able to understand BertF awesome explanation. List < ? extends Number > means ? could be of any type extending Number(Integer, Double, etc) and its not clearified in declaration ( List < ? extends Number > list ) that which of them it is, so when u wanna use add method its not known if the input is of the same type or not; what is the type at all?

    So the elements of List < ? extends Number > could only be set when constructing.

    Also note this: When we're using templates we are telling the compiler what type we're messing with. T for example holds that type for us, but not ? does the same

    I gotta say.. This is one of the dirty ones to explain/learn

    0 讨论(0)
  • 2020-11-22 03:20

    "List '<' ? extends Number> is actually an upper bound wildcard !

    The upper-bounded wildcard says that any class that extends Number or Number itself can be used as the formal parameter type: The problem stems from the fact that Java doesn’t know what type List really is. It has to be an EXACT and UNIQUE Type. I hope it helps :)

    0 讨论(0)
提交回复
热议问题