¿Cómo puedo agregar a la lista <? extiende Número> estructuras de datos?

154

Tengo una lista que se declara así:

 List<? extends Number> foo3 = new ArrayList<Integer>();

Traté de agregar 3 a foo3. Sin embargo, recibo un mensaje de error como este:

The method add(capture#1-of ? extends Number) in the type List<capture#1-of ?
extends Number> is not applicable for the arguments (ExtendsNumber)
unj2
fuente
61
Tenga en cuenta que List<? extends Number>no significa "lista de objetos de diferentes tipos, todos los cuales se extienden Number". Significa "lista de objetos de un solo tipo que se extiende Number".
Pavel Minaev
1
Será mejor que verifique la regla PECS, el productor se extiende, el consumidor súper. stackoverflow.com/questions/2723397/…
noego

Respuestas:

303

Lo siento, pero no puedes.

La declaración comodín de List<? extends Number> foo3significa que la variable foo3puede contener cualquier valor de una familia de tipos (en lugar de cualquier valor de un tipo específico). Significa que cualquiera de estos son tareas legales:

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

Entonces, dado esto, qué tipo de objeto podría agregar a List foo3eso sería legal después de cualquiera de las posibles ArrayListasignaciones anteriores :

  • No puede agregar un Integerporque foo3podría estar apuntando a un List<Double>.
  • No puede agregar un Doubleporque foo3podría estar apuntando a un List<Integer>.
  • No puede agregar un Numberporque foo3podría estar apuntando a un List<Integer>.

No puede agregar ningún objeto List<? extends T>porque no puede garantizar a qué tipo de Listseñal realmente está apuntando, por lo que no puede garantizar que el objeto esté permitido en eso List. La única "garantía" es que solo puede leer de ella y obtendrá una To subclase de T.

La lógica inversa se aplica a super, por ejemplo List<? super T>. Estos son legales:

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

No puede leer el tipo específico T (p Number. Ej. ) List<? super T>Porque no puede garantizar a qué tipo de Listapunta realmente. La única "garantía" que tiene es que puede agregar un valor de tipo T(o cualquier subclase de T) sin violar la integridad de la lista a la que apunta.


El ejemplo perfecto de esto es la firma de Collections.copy():

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

Observe cómo la srcdeclaración de la lista usa extendspara permitirme pasar cualquier Lista de una familia de tipos de Lista relacionados y aún así garantizar que producirá valores de tipo T o subclases de T. Pero no puede agregar a la srclista.

La destdeclaración de la lista superme permite pasar cualquier Lista de una familia de tipos de Lista relacionados y aún así garantizar que pueda escribir un valor de un tipo T específico en esa lista. Pero no se puede garantizar la lectura de los valores de tipo específico T si leo de la lista.

Entonces, gracias a los comodines genéricos, puedo hacer cualquiera de estas llamadas con ese único método:

// 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());

Considere este código confuso y muy amplio para ejercitar su cerebro. Las líneas comentadas son ilegales y la razón por la cual se indica en el extremo derecho de la línea (es necesario desplazarse para ver algunas de ellas):

  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>
Bert F
fuente
11
¿Por qué se nos permite escribir una oración como `Lista <? extiende Number> foo3 = new ArrayList <Integer> (); `si no podemos seguir agregando elementos a esta lista?
Amit Kumar Gupta
77
@articlestack: Agregar no es lo único útil para hacer con una lista. Una vez que se llena una lista, leer de una lista puede ser útil. Los comodines genéricos permiten escribir código que funciona genéricamente para una familia de listas, por ejemplo, funciona para List <Integer> o List <Double>. Por ejemplo, mire la firma de Collection.copy (). El src Listargumento se usa extendspara leer de la lista src, mientras que el dest Listargumento se usa superpara escribir en la lista de destino. Esto permite un método que puede copiar desde List<Integer>o List<Double>hacia List<Number>o List<Object>.
Bert F
Bert F, perdón por un comentario tardío. ¿Hay un error tipográfico en su pasaje, "para agregar un valor de tipo T (o subclase de T)" debería leer (o superclase de T)?
Vortex
sí, también lo vi @Vortex No estoy seguro de si es correcto o lo leí mal.
Ungeheuer
1
@Vortex: or subclass of Tes correcto. Por ejemplo, no puedo agregar un an Object(superclase de Number) List<? super Number> foo3porque foo3puede haber sido asignado como: List<? super Number> foo3 = new ArrayList<Number>(que puede contener solo Numbero subclases de Number). <? super Number>se refiere a los tipos de List<>s que se pueden asignar foo3, no a los tipos de cosas que se pueden agregar / leer de él. Los tipos de cosas que se pueden agregar / quitar foo3deben ser cosas que se pueden agregar / quitar de cualquier tipo de cosas a las que se List<>pueda asignar foo3.
Bert F
17

No puedes (sin yesos inseguros). Solo puedes leer de ellos.

El problema es que no sabes de qué es exactamente la lista. Podría ser una lista de cualquier subclase de Número, por lo que cuando intentas poner un elemento en él, no sabes que el elemento realmente se ajusta a la lista.

Por ejemplo, la Lista puede ser una lista de Bytes, por lo que sería un error ponerle una Float.

sepp2k
fuente
2

"La lista '<'? Extiende Número> es en realidad un comodín de límite superior.

El comodín de límite superior dice que cualquier clase que amplíe Número o Número en sí puede usarse como el tipo de parámetro formal: el problema se debe al hecho de que Java no sabe qué tipo de Lista es realmente. Tiene que ser un tipo EXACTO y ÚNICO. Espero que ayude :)

andygro
fuente
2

Me ha resultado confuso aunque leí las respuestas aquí, hasta que encontré el comentario de Pavel Minaev:

Tenga en cuenta que la Lista <? extiende Número> no significa "lista de objetos de diferentes tipos, todos los cuales extienden Número". Significa "lista de objetos de un solo tipo que extiende Número"

Después de esto pude entender la increíble explicación de BertF. Lista <? extiende Número> medios? podría ser de cualquier tipo Número extendido (Entero, Doble, etc.) y no está aclarado en la declaración (Lista <? extiende Número> lista) cuál de ellos es, así que cuando quieres usar el método de agregar no se sabe si la entrada es del mismo tipo o no; ¿De qué tipo es?

Entonces, ¿los elementos de List <? extiende Número> solo se puede establecer al construir.

También tenga en cuenta esto: cuando usamos plantillas, le estamos diciendo al compilador con qué tipo estamos jugando. T, por ejemplo, tiene ese tipo para nosotros, pero no ? hace lo mismo

Tengo que decir ... Este es uno de los sucios para explicar / aprender

navid
fuente
1

Podrías hacer esto en su lugar:

  List<Number> foo3 = new ArrayList<Number>();      
  foo3.add(3);
Ryan Elkins
fuente
0

Puede evitarlo creando una referencia a la Lista con un tipo diferente.

(Estos son los "moldes inseguros" mencionados por 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);

Porque untypedList, superclassedListy trulyclassedListson solo referencias a list, todavía estará agregando elementos a la ArrayList original.

En realidad, no es necesario usarlo (List<?>)en el ejemplo anterior, pero puede que lo necesite en su código, dependiendo del tipo que listle hayan dado.

Tenga en cuenta que el uso ?le dará advertencias del compilador, hasta que ponga esto por encima de su función:

@SuppressWarnings("unchecked")
joeytwiddle
fuente
-1

donde extender la lista desde 'Objeto', puede usar list.add y cuando lo desee usar list.get solo necesita lanzar Object a su Object;

abbasalim
fuente