Diferencia entre <? súper T> y <? extiende T> en Java [duplicado]

Resuelto Anand asked hace 14 años • 13 respuestas

¿ Cuál es la diferencia entre List<? super T>y List<? extends T>?

Solía ​​​​usar List<? extends T>, pero no me permite agregarle elementos list.add(e), mientras que List<? super T>sí.

Anand avatar Dec 03 '10 13:12 Anand
Aceptado

extends

La declaración comodín de List<? extends Number> foo3significa que cualquiera de estas son asignaciones legales:

List<? extends Number> foo3 = new ArrayList<Number>();  // Number "extends" Number (in this context)
List<? extends Number> foo3 = new ArrayList<Integer>(); // Integer extends Number
List<? extends Number> foo3 = new ArrayList<Double>();  // Double extends Number
  1. Lectura : dadas las posibles asignaciones anteriores, ¿de qué tipo de objeto se garantiza que leerá List foo3?

    • Puede leer a Numberporque cualquiera de las listas que podrían asignarse para foo3contener una Numbero una subclase de Number.
    • No puedes leer un Integerporque foo3podría estar apuntando a un List<Double>.
    • No puedes leer a Doubleporque foo3podría estar apuntando a un List<Integer>.
  2. Escritura : dadas las posibles asignaciones anteriores, ¿qué tipo de objeto podría agregar List foo3que sería legal para todasArrayList las posibles asignaciones anteriores ?

    • No puedes agregar un Integerporque foo3podría estar apuntando a un List<Double>.
    • No puedes agregar un Doubleporque foo3podría estar apuntando a un List<Integer>.
    • No puedes 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 Listapunta realmente, por lo que no puede garantizar que el objeto esté permitido en ese List. La única "garantía" es que solo puedes leerlo y obtendrás una Tsubclase o de T.

super

Ahora considere List <? super T>.

La declaración comodín de List<? super Integer> foo3significa que cualquiera de estas son asignaciones legales:

List<? super Integer> foo3 = new ArrayList<Integer>();  // Integer is a "superclass" of Integer (in this context)
List<? super Integer> foo3 = new ArrayList<Number>();   // Number is a superclass of Integer
List<? super Integer> foo3 = new ArrayList<Object>();   // Object is a superclass of Integer
  1. Lectura : dadas las posibles asignaciones anteriores, ¿qué tipo de objeto tiene la garantía de recibir cuando lee List foo3?

    • No se le garantiza que un Integerporque foo3podría estar apuntando a List<Number>o List<Object>.
    • No tienes garantizado un Numberporque foo3podría estar apuntando a un List<Object>.
    • La única garantía es que obtendrá una instancia de una Objectsubclase o de Object(pero no sabe qué subclase).
  2. Escritura : dadas las posibles asignaciones anteriores, ¿qué tipo de objeto podría agregar List foo3que sería legal para todasArrayList las posibles asignaciones anteriores ?

    • Puede agregar un Integerporque Integerestá permitido en cualquiera de las listas anteriores.
    • Puede agregar una instancia de una subclase de porque se permite Integeruna instancia de una subclase de en cualquiera de las listas anteriores.Integer
    • No puedes agregar un Doubleporque foo3podría estar apuntando a un ArrayList<Integer>.
    • No puedes agregar un Numberporque foo3podría estar apuntando a un ArrayList<Integer>.
    • No puedes agregar un Objectporque foo3podría estar apuntando a un ArrayList<Integer>.

PECS

Recuerde PECS : "El productor extiende, el consumidor supera" .

  • "El productor extiende" : si necesita que un Listproduzca Tvalores (desea leer Tlos mensajes de la lista), debe declararlo con ? extends T, por ejemplo List<? extends Integer>. Pero no puedes agregar a esta lista.

  • "Consumer Super" : si necesita un Listpara consumir Tvalores (desea escribir Ts en la lista), debe declararlo con ? super T, por ejemplo List<? super Integer>. Pero no hay garantías de qué tipo de objeto podrá leer en esta lista.

  • Si necesita leer y escribir en una lista, debe declararla exactamente sin comodines, por ejemplo List<Integer>.

Ejemplo

Tenga en cuenta este ejemplo de las preguntas frecuentes sobre genéricos de Java . Observe cómo se utiliza la lista de origen src(la lista de producción) extendsy la lista de destino dest(la lista de consumo) super:

public class Collections { 
  public static <T> void copy(List<? super T> dest, List<? extends T> src) {
      for (int i = 0; i < src.size(); i++) 
        dest.set(i, src.get(i)); 
  } 
}

Consulte también ¿Cómo puedo agregar a la Lista<? extiende Número> estructuras de datos?

Bert F avatar Dec 03 '2010 08:12 Bert F

Imagina tener esta jerarquía

ingrese la descripción de la imagen aquí

1. Se extiende

Al escribir

    List<? extends C2> list;

estás diciendo que listpodrá hacer referencia a un objeto de tipo (por ejemplo) ArrayListcuyo tipo genérico sea uno de los 7 subtipos de C2( C2incluido):

  1. C2: new ArrayList<C2>(); , (un objeto que puede almacenar C2 o subtipos) o
  2. D1: new ArrayList<D1>(); , (un objeto que puede almacenar D1 o subtipos) o
  3. D2: new ArrayList<D2>(); , (un objeto que puede almacenar D2 o subtipos) o...

etcétera. Siete casos diferentes:

    1) new ArrayList<C2>(): can store C2 D1 D2 E1 E2 E3 E4
    2) new ArrayList<D1>(): can store    D1    E1 E2  
    3) new ArrayList<D2>(): can store       D2       E3 E4
    4) new ArrayList<E1>(): can store          E1             
    5) new ArrayList<E2>(): can store             E2             
    6) new ArrayList<E3>(): can store                E3             
    7) new ArrayList<E4>(): can store                   E4             

Disponemos de un conjunto de tipos "almacenables" para cada caso posible: 7 conjuntos (rojos) aquí representados gráficamente

ingrese la descripción de la imagen aquí

Como puedes ver, no existe un tipo seguro que sea común a todos los casos:

  • no puedes list.add(new C2(){});porque podría serlist = new ArrayList<D1>();
  • no puedes list.add(new D1(){});porque podría serlist = new ArrayList<D2>();

etcétera.

2. Súper

Al escribir

    List<? super C2> list;

estás diciendo que listpodrás hacer referencia a un objeto de tipo (por ejemplo) ArrayListcuyo tipo genérico sea uno de los 7 supertipos de C2( C2incluido):

  • A1: new ArrayList<A1>(); , (un objeto que puede almacenar A1 o subtipos) o
  • A2: new ArrayList<A2>(); , (un objeto que puede almacenar A2 o subtipos) o
  • A3: new ArrayList<A3>(); , (un objeto que puede almacenar A3 o subtipos) o...

etcétera. Siete casos diferentes:

    1) new ArrayList<A1>(): can store A1          B1 B2       C1 C2    D1 D2 E1 E2 E3 E4
    2) new ArrayList<A2>(): can store    A2          B2       C1 C2    D1 D2 E1 E2 E3 E4
    3) new ArrayList<A3>(): can store       A3          B3       C2 C3 D1 D2 E1 E2 E3 E4
    4) new ArrayList<A4>(): can store          A4       B3 B4    C2 C3 D1 D2 E1 E2 E3 E4
    5) new ArrayList<B2>(): can store                B2       C1 C2    D1 D2 E1 E2 E3 E4
    6) new ArrayList<B3>(): can store                   B3       C2 C3 D1 D2 E1 E2 E3 E4
    7) new ArrayList<C2>(): can store                            C2    D1 D2 E1 E2 E3 E4

Disponemos de un conjunto de tipos "almacenables" para cada caso posible: 7 conjuntos (rojos) aquí representados gráficamente

ingrese la descripción de la imagen aquí

Como puedes ver, aquí tenemos siete tipos seguros que son comunes a todos los casos: C2, D1, D2, E1, E2, E3, E4.

  • puedes list.add(new C2(){});porque, independientemente del tipo de Lista a la que hagamos referencia, C2está permitido
  • puedes list.add(new D1(){});porque, independientemente del tipo de Lista a la que hagamos referencia, D1está permitido

etcétera. Probablemente hayas notado que estos tipos corresponden a la jerarquía que comienza desde tipo C2.

Notas

Aquí la jerarquía completa si deseas hacer algunas pruebas.

interface A1{}
interface A2{}
interface A3{}
interface A4{}

interface B1 extends A1{}
interface B2 extends A1,A2{}
interface B3 extends A3,A4{}
interface B4 extends A4{}

interface C1 extends B2{}
interface C2 extends B2,B3{}
interface C3 extends B3{}

interface D1 extends C1,C2{}
interface D2 extends C2{}

interface E1 extends D1{}
interface E2 extends D1{}
interface E3 extends D2{}
interface E4 extends D2{}
Luigi Cortese avatar Oct 20 '2015 21:10 Luigi Cortese

Me encanta la respuesta de @Bert F pero así es como lo ve mi cerebro.

Tengo una X en la mano. Si quiero escribir mi X en una Lista, esa Lista debe ser una Lista de X o una Lista de cosas a las que se puede actualizar mi X mientras las escribo, es decir, cualquier superclase de X...

List<? super   X>

Si obtengo una Lista y quiero leer una X de esa Lista, será mejor que sea una Lista de X o una Lista de cosas que pueden actualizarse a X a medida que las leo, es decir, cualquier cosa que extienda X

List<? extends X>
Michael Dausmann avatar Dec 03 '2015 22:12 Michael Dausmann

Me gustaría visualizar la diferencia. Supongamos que tenemos:

class A { }
class B extends A { }
class C extends B { }

List<? extends T>- leer y asignar:

|-------------------------|-------------------|---------------------------------|
|         wildcard        |        get        |              assign             |
|-------------------------|-------------------|---------------------------------|
|    List<? extends C>    |    A    B    C    |                       List<C>   |
|-------------------------|-------------------|---------------------------------|
|    List<? extends B>    |    A    B         |             List<B>   List<C>   |
|-------------------------|-------------------|---------------------------------|
|    List<? extends A>    |    A              |   List<A>   List<B>   List<C>   |
|-------------------------|-------------------|---------------------------------|

List<? super T>- escribir y asignar:

|-------------------------|-------------------|-------------------------------------------|
|         wildcard        |        add        |                   assign                  |
|-------------------------|-------------------|-------------------------------------------|
|     List<? super C>     |              C    |  List<Object>  List<A>  List<B>  List<C>  |
|-------------------------|-------------------|-------------------------------------------|
|     List<? super B>     |         B    C    |  List<Object>  List<A>  List<B>           |
|-------------------------|-------------------|-------------------------------------------|
|     List<? super A>     |    A    B    C    |  List<Object>  List<A>                    |
|-------------------------|-------------------|-------------------------------------------|

En todos los casos:

  • siempre puedes obtener Object de una lista independientemente del comodín.
  • siempre puedes agregar null a una lista mutable independientemente del comodín.
Oleksandr Pyrohov avatar Sep 23 '2018 17:09 Oleksandr Pyrohov

Según la respuesta de Bert F, me gustaría explicar lo que entendí.

Digamos que tenemos 3 clases como

public class Fruit{}

public class Melon extends Fruit{}

public class WaterMelon extends Melon{}

Aquí tenemos

List<? extends Fruit> fruitExtendedList = …

//Says that I can be a list of any object as long as this object extends Fruit.

Ok, ahora intentemos obtener algún valor de fruitExtendedList

Fruit fruit = fruitExtendedList.get(position)

//This is valid as it can only return Fruit or its subclass.

De nuevo intentemos

Melon melon = fruitExtendedList.get(position)

//This is not valid because fruitExtendedList can be a list of Fruit only, it may not be 
//list of Melon or WaterMelon and in java we cannot assign sub class object to 
//super class object reference without explicitly casting it.

Lo mismo ocurre con

WaterMelon waterMelon = fruitExtendedList.get(position)

Ahora intentemos configurar algún objeto en fruitExtendedList

Agregar objeto de fruta

fruitExtendedList.add(new Fruit())

//This in not valid because as we know fruitExtendedList can be a list of any 
//object as long as this object extends Fruit. So what if it was the list of  
//WaterMelon or Melon you cannot add Fruit to the list of WaterMelon or Melon.

Agregar objeto Melon

fruitExtendedList.add(new Melon())

//This would be valid if fruitExtendedList was the list of Fruit but it may 
//not be, as it can also be the list of WaterMelon object. So, we see an invalid 
//condition already.

Finalmente intentemos agregar el objeto WaterMelon.

fruitExtendedList.add(new WaterMelon())

//Ok, we got it now we can finally write to fruitExtendedList as WaterMelon 
//can be added to the list of Fruit or Melon as any superclass reference can point 
//to its subclass object.

Pero espera, ¿qué pasa si alguien decide hacer un nuevo tipo de limón, digamos por argumentos? SaltyLemon como

public class SaltyLemon extends Lemon{}

Ahora fruitExtendedList puede ser una lista de frutas, melón, sandía o limón salado.

Entonces, nuestra declaración

fruitExtendedList.add(new WaterMelon())

tampoco es válido.

Básicamente podemos decir que no podemos escribir nada en una lista extendida de frutas.

Esto resumeList<? extends Fruit>

Ahora veamos

List<? super Melon> melonSuperList= …

//Says that I can be a list of anything as long as its object has super class of Melon.

Ahora intentemos obtener algún valor de melonSuperList

Fruit fruit = melonSuperList.get(position)

//This is not valid as melonSuperList can be a list of Object as in java all 
//the object extends from Object class. So, Object can be super class of Melon and 
//melonSuperList can be a list of Object type

Del mismo modo, Melon, WaterMelon o cualquier otro objeto no se pueden leer.

Pero tenga en cuenta que podemos leer instancias de tipo de objeto

Object myObject = melonSuperList.get(position)

//This is valid because Object cannot have any super class and above statement 
//can return only Fruit, Melon, WaterMelon or Object they all can be referenced by
//Object type reference.

Ahora, intentemos establecer algún valor de melonSuperList.

Agregar objeto tipo objeto

melonSuperList.add(new Object())

//This is not valid as melonSuperList can be a list of Fruit or Melon.
//Note that Melon itself can be considered as super class of Melon.

Agregar objeto tipo fruta

melonSuperList.add(new Fruit())

//This is also not valid as melonSuperList can be list of Melon

Agregar objeto tipo Melon

melonSuperList.add(new Melon())

//This is valid because melonSuperList can be list of Object, Fruit or Melon and in 
//this entire list we can add Melon type object.

Agregar objeto tipo WaterMelon

melonSuperList.add(new WaterMelon())

//This is also valid because of same reason as adding Melon

Para resumir, podemos agregar Melon o su subclase en melonSuperList y leer solo el objeto de tipo Objeto.

Sushant avatar Mar 19 '2016 08:03 Sushant