Diferencia entre <? súper T> y <? extiende T> en Java [duplicado]
¿ 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í.
extends
La declaración comodín de List<? extends Number> foo3
significa 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
Lectura : dadas las posibles asignaciones anteriores, ¿de qué tipo de objeto se garantiza que leerá
List foo3
?- Puede leer a
Number
porque cualquiera de las listas que podrían asignarse parafoo3
contener unaNumber
o una subclase deNumber
. - No puedes leer un
Integer
porquefoo3
podría estar apuntando a unList<Double>
. - No puedes leer a
Double
porquefoo3
podría estar apuntando a unList<Integer>
.
- Puede leer a
Escritura : dadas las posibles asignaciones anteriores, ¿qué tipo de objeto podría agregar
List foo3
que sería legal para todasArrayList
las posibles asignaciones anteriores ?- No puedes agregar un
Integer
porquefoo3
podría estar apuntando a unList<Double>
. - No puedes agregar un
Double
porquefoo3
podría estar apuntando a unList<Integer>
. - No puedes agregar un
Number
porquefoo3
podría estar apuntando a unList<Integer>
.
- No puedes agregar un
No puede agregar ningún objeto List<? extends T>
porque no puede garantizar a qué tipo List
apunta 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 T
subclase o de T
.
super
Ahora considere List <? super T>
.
La declaración comodín de List<? super Integer> foo3
significa 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
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
Integer
porquefoo3
podría estar apuntando aList<Number>
oList<Object>
. - No tienes garantizado un
Number
porquefoo3
podría estar apuntando a unList<Object>
. - La única garantía es que obtendrá una instancia de una
Object
subclase o deObject
(pero no sabe qué subclase).
- No se le garantiza que un
Escritura : dadas las posibles asignaciones anteriores, ¿qué tipo de objeto podría agregar
List foo3
que sería legal para todasArrayList
las posibles asignaciones anteriores ?- Puede agregar un
Integer
porqueInteger
está permitido en cualquiera de las listas anteriores. - Puede agregar una instancia de una subclase de porque se permite
Integer
una instancia de una subclase de en cualquiera de las listas anteriores.Integer
- No puedes agregar un
Double
porquefoo3
podría estar apuntando a unArrayList<Integer>
. - No puedes agregar un
Number
porquefoo3
podría estar apuntando a unArrayList<Integer>
. - No puedes agregar un
Object
porquefoo3
podría estar apuntando a unArrayList<Integer>
.
- Puede agregar un
PECS
Recuerde PECS : "El productor extiende, el consumidor supera" .
"El productor extiende" : si necesita que un
List
produzcaT
valores (desea leerT
los mensajes de la lista), debe declararlo con? extends T
, por ejemploList<? extends Integer>
. Pero no puedes agregar a esta lista."Consumer Super" : si necesita un
List
para consumirT
valores (desea escribirT
s en la lista), debe declararlo con? super T
, por ejemploList<? 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) extends
y 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?
Imagina tener esta jerarquía
1. Se extiende
Al escribir
List<? extends C2> list;
estás diciendo que list
podrá hacer referencia a un objeto de tipo (por ejemplo) ArrayList
cuyo tipo genérico sea uno de los 7 subtipos de C2
( C2
incluido):
- C2:
new ArrayList<C2>();
, (un objeto que puede almacenar C2 o subtipos) o - D1:
new ArrayList<D1>();
, (un objeto que puede almacenar D1 o subtipos) o - 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
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 list
podrás hacer referencia a un objeto de tipo (por ejemplo) ArrayList
cuyo tipo genérico sea uno de los 7 supertipos de C2
( C2
incluido):
- 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
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,C2
está permitido - puedes
list.add(new D1(){});
porque, independientemente del tipo de Lista a la que hagamos referencia,D1
está 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{}
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>
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.
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.