java - parameter - Lista<Map<String, String>> vs List<? extiende el Mapa<String, String>>
java generics parameter (5)
¿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
? extends
?
Como mencionaste, podría haber dos versiones a continuación 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 quiera tener un mapa de un tipo dado. En caso de que alguien coloque accidentalmente un tipo de mapa diferente, por ejemplo, Map<String, int>
. Su método de consumo podría romperse.
Para garantizar que List
pueda contener objetos de un tipo determinado, ? extends
introducen los genéricos de Java ? extends
? extends
. Por lo tanto, en el n. ° 1, la List
puede contener cualquier objeto que se derive del tipo Map<String, String>
. Agregar cualquier otro tipo de datos arrojaría una excepción.
Hoy, he usado esta característica, así que aquí está mi ejemplo muy real de la vida real. (He cambiado los nombres de clase y método por genéricos para que no distraigan del punto real).
Tengo un método que pretende aceptar un Set
de objetos A
que originalmente escribí con esta firma:
void myMethod(Set<A> set)
Pero realmente quiere llamarlo con Set
s de subclases de A
¡Pero esto no está permitido! (El motivo es que myMethod
podría agregar objetos al set
que son del tipo A
, pero no del subtipo en el que se declaran los objetos del set
en el sitio de la persona que llama, por lo que podría romper el sistema del tipo si fuera posible. )
Ahora aquí vienen los genéricos al rescate, porque funciona como estaba previsto si utilizo esta firma de 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 forma, el tipo de conjunto se convierte en una colección de objetos del subtipo real de A
, por lo que es posible usarlo con subclases sin poner en peligro el sistema de tipos.
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>>
Asi que:
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
}
Uno pensaría que una List
de HashMap
s debería ser una List
de Map
, pero hay una buena razón por la cual no es:
Supongamos que puedes 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)
Así que esta es la razón por la cual una List
de HashMap
s no debe ser una List
de Map
s.
Lo que me faltan en las otras respuestas es una referencia a cómo esto se relaciona con co y contravariancia y sub y supertipos (es decir, polimorfismo) en general y con Java en particular. Esto puede ser bien entendido por el OP, pero por las dudas, aquí va:
Covarianza
Si tiene un Automobile
clase, entonces el Car
y el 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 a usar 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ía que funcionara 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.
El motivo del error es, sin embargo, correcto: 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 heredada. 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
Contravariancia
El reverso de la co-varianza es la contravariancia. Cuando en covarianza los tipos de parámetros deben tener una relación de subtipo, en contravarianza deben tener una relación de supertipo. Esto se puede considerar como un límite superior de herencia: se permite cualquier supertipo 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 compare objetos y los use con cualquier tipo.
¿Cuándo usar contra o co-varianza?
Un poco OT quizás, no preguntaste, pero ayuda a entender respondiendo tu pregunta. En general, cuando obtienes algo, usa la covarianza y cuando pones algo, usa la contravariancia. Esto se explica mejor en una respuesta a la pregunta de desbordamiento de pila ¿ Cómo se usaría la contravariancia en los genéricos de Java? .
Entonces, ¿qué es entonces con List<? extends Map<String, String>>
List<? extends Map<String, String>>
Utiliza extends
, por lo que se aplican las reglas de covarianza . Aquí tiene una lista de mapas y cada elemento que almacena en la lista debe ser un Map<string, string>
o derivar de ella. La instrucción List<Map<String, String>>
no puede derivar de Map
, pero debe ser un 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
¿Qué más?
Esto es probablemente obvio, pero es posible que ya haya notado que el uso de la palabra clave extends
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
Supongamos que desea permitir cualquier tipo en el mapa, con una clave como cadena, puede usar extend
en cada parámetro de tipo. Es decir, supongamos 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>());
No puede asignar expresiones con tipos como List<NavigableMap<String,String>>
al primero.
(Si desea saber por qué no puede asignar List<String>
a List<Object>
vea un trillón de otras preguntas en SO.)