wildcards parameter objects method generic bounded java generics inheritance polymorphism

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:

  1. List<? extends Map<String, String>>
  2. 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.)