¿Hay alguna diferencia entre
List<Map<String, String>>
y
List<? extends Map<String, String>>
?
Si no hay diferencia, ¿cuál es el beneficio de usar ? extends
?
¿Hay alguna diferencia entre
List<Map<String, String>>
y
List<? extends Map<String, String>>
?
Si no hay diferencia, ¿cuál es el beneficio de usar ? extends
?
Respuestas:
La diferencia es que, por ejemplo, un
List<HashMap<String,String>>
es un
List<? extends Map<String,String>>
pero no un
List<Map<String,String>>
Entonces:
void withWilds( List<? extends Map<String,String>> foo ){}
void noWilds( List<Map<String,String>> foo ){}
void main( String[] args ){
List<HashMap<String,String>> myMap;
withWilds( myMap ); // Works
noWilds( myMap ); // Compiler error
}
Se podría pensar que una List
de HashMap
s debería ser una List
de Map
s, pero hay una buena razón por la cual no lo es:
Supongamos que pudieras hacer:
List<HashMap<String,String>> hashMaps = new ArrayList<HashMap<String,String>>();
List<Map<String,String>> maps = hashMaps; // Won't compile,
// but imagine that it could
Map<String,String> aMap = Collections.singletonMap("foo","bar"); // Not a HashMap
maps.add( aMap ); // Perfectly legal (adding a Map to a List of Maps)
// But maps and hashMaps are the same object, so this should be the same as
hashMaps.add( aMap ); // Should be illegal (aMap is not a HashMap)
Entonces esta es la razón por la cual una List
de HashMap
s no debería ser una List
de Map
s.
HashMap
se Map
debe al polimorfismo.
List
de HashMap
s no es una List
de Map
s.
List<Map<String,String>> maps = hashMaps;
y HashMap<String,String> aMap = new HashMap<String, String>();
, todavía encontrará que maps.add(aMap);
es ilegal mientras que hashMaps.add(aMap);
es legal. El propósito es evitar la adición de tipos incorrectos, pero no permitirá la adición de los tipos correctos (el compilador no puede determinar el tipo "correcto" durante el tiempo de compilación)
HashMap
a una lista de Map
s, ambos de sus ejemplos son legales, si yo los estoy leyendo correctamente.
No puede asignar expresiones con tipos como List<NavigableMap<String,String>>
el primero.
(Si desea saber por qué no puede asignar List<String>
para List<Object>
ver un millón de otras preguntas sobre SO).
List<String>
no es un subtipo de List<Object>
? - ver, por ejemplo, stackoverflow.com/questions/3246137/…
? extends
. Tampoco explica la correlación con super / subtipos o co / contravarianza (si hay alguna).
Lo que me falta en las otras respuestas es una referencia a cómo esto se relaciona con la covarianza y la contravarianza y los sub y supertipos (es decir, el polimorfismo) en general y Java en particular. Esto puede ser bien entendido por el OP, pero por si acaso, aquí va:
Si tienes una clase Automobile
, entonces Car
y Truck
son sus subtipos. Cualquier automóvil puede asignarse a una variable de tipo Automóvil, esto es bien conocido en OO y se llama polimorfismo. La covarianza se refiere al uso de este mismo principio en escenarios con genéricos o delegados. Java no tiene delegados (todavía), por lo que el término se aplica solo a los genéricos.
Tiendo a pensar en la covarianza como polimorfismo estándar, lo que esperarías que funcione sin pensar, porque:
List<Car> cars;
List<Automobile> automobiles = cars;
// You'd expect this to work because Car is-a Automobile, but
// throws inconvertible types compile error.
Sin embargo, la razón del error es correcta: List<Car>
no hereda de List<Automobile>
y, por lo tanto, no se pueden asignar entre sí. Solo los parámetros de tipo genérico tienen una relación de herencia. Uno podría pensar que el compilador de Java simplemente no es lo suficientemente inteligente como para comprender adecuadamente su escenario allí. Sin embargo, puedes ayudar al compilador dándole una pista:
List<Car> cars;
List<? extends Automobile> automobiles = cars; // no error
El reverso de la covarianza es la contravarianza. Donde en covarianza los tipos de parámetros deben tener una relación de subtipo, en contravarianza deben tener una relación de supertipo. Esto puede considerarse como un límite superior de herencia: cualquier supertipo está permitido e incluye el tipo especificado:
class AutoColorComparer implements Comparator<Automobile>
public int compare(Automobile a, Automobile b) {
// Return comparison of colors
}
Esto se puede usar con Collections.sort :
public static <T> void sort(List<T> list, Comparator<? super T> c)
// Which you can call like this, without errors:
List<Car> cars = getListFromSomewhere();
Collections.sort(cars, new AutoColorComparer());
Incluso podría llamarlo con un comparador que compara objetos y usarlo con cualquier tipo.
Un poco OT quizás no lo preguntaste, pero ayuda a entender la respuesta a tu pregunta. En general, cuando obtienes algo, usa covarianza y cuando pones algo, usa contravarianza. Esto se explica mejor en una respuesta a la pregunta de desbordamiento de pila ¿Cómo se puede utilizar en contravarianza genéricos de Java? .
List<? extends Map<String, String>>
Usas extends
, entonces se aplican las reglas para la covarianza . Aquí tiene una lista de mapas y cada elemento que almacene en la lista debe ser un Map<string, string>
derivado de él. La declaración List<Map<String, String>>
no puede derivar de Map
, pero debe ser a Map
.
Por lo tanto, lo siguiente funcionará, porque TreeMap
hereda de Map
:
List<Map<String, String>> mapList = new ArrayList<Map<String, String>>();
mapList.add(new TreeMap<String, String>());
pero esto no:
List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>();
mapList.add(new TreeMap<String, String>());
y esto tampoco funcionará, porque no satisface la restricción de covarianza:
List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>();
mapList.add(new ArrayList<String>()); // This is NOT allowed, List does not implement Map
Esto probablemente sea obvio, pero es posible que ya haya notado que usar la extends
palabra clave solo se aplica a ese parámetro y no al resto. Es decir, lo siguiente no se compilará:
List<? extends Map<String, String>> mapList = new List<? extends Map<String, String>>();
mapList.add(new TreeMap<String, Element>()) // This is NOT allowed
Suponga que desea permitir cualquier tipo en el mapa, con una clave como cadena, puede usar extend
en cada parámetro de tipo. Es decir, suponga que procesa XML y desea almacenar AttrNode, Element, etc. en un mapa, puede hacer algo como:
List<? extends Map<String, ? extends Node>> listOfMapsOfNodes = new...;
// Now you can do:
listOfMapsOfNodes.add(new TreeMap<Sting, Element>());
listOfMapsOfNodes.add(new TreeMap<Sting, CDATASection>());
List<? extends Map<String, String>> mapList = new ArrayList<? extends Map<String, String>>(); mapList.add(new TreeMap<String, String>());
resultados en found: ? extends java.util.Map<java.lang.String,java.lang.String> required: class or interface without bounds
. List<Map<String, String>> mapList = new ArrayList<Map<String, String>>(); mapList.add(new TreeMap<String, String>());
funciona perfectamente. El último ejemplo es obviamente correcto.
Hoy, he usado esta función, así que aquí está mi ejemplo muy fresco de la vida real. (He cambiado los nombres de clase y método a genéricos para que no distraigan del punto real).
Tengo un método que ha significado para aceptar una Set
de A
objetos que originalmente escribí con esta firma:
void myMethod(Set<A> set)
Pero en realidad quiere llamarlo con Set
s de subclases de A
. ¡Pero esto no está permitido! (La razón de esto es que myMethod
podría agregar objetos set
que sean de tipo A
, pero no del subtipo que set
se declara que los objetos están en el sitio de la persona que llama. Por lo tanto, esto podría romper el sistema de tipos si fuera posible).
Ahora aquí vienen los genéricos al rescate, porque funciona según lo previsto si uso la firma de este método en su lugar:
<T extends A> void myMethod(Set<T> set)
o más corto, si no necesita usar el tipo real en el cuerpo del método:
void myMethod(Set<? extends A> set)
De esta manera, set
el tipo se convierte en una colección de objetos del subtipo real de A
, por lo que es posible usar esto con subclases sin poner en peligro el sistema de tipos.
Como mencionó, podría haber dos versiones siguientes de definir una Lista:
List<? extends Map<String, String>>
List<?>
2 es muy abierto. Puede contener cualquier tipo de objeto. Esto puede no ser útil en caso de que desee tener un mapa de un tipo dado. En caso de que alguien accidentalmente ponga un tipo diferente de mapa, por ejemplo Map<String, int>
,. Su método de consumo podría romperse.
Para garantizar que List
pueda contener objetos de un tipo dado, se introdujeron los genéricos de Java ? extends
. Entonces, en el n. ° 1, List
puede contener cualquier objeto derivado del Map<String, String>
tipo. Agregar cualquier otro tipo de datos arrojaría una excepción.