Lista <Mapa <Cadena, Cadena >> vs Lista <? extiende Mapa <Cadena, Cadena >>

129

¿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?

Eng.Fouad
fuente
2
Me encanta Java, pero esta es una de las cosas que no es tan buena ...
Mite Mitreski
44
Siento que si lo leemos como "cualquier cosa que se extienda ...", queda claro.
Basura
¡Increíble, más de 12K vistas en aproximadamente 3 días!
Eng.Fouad
55
Llegó a la portada de Hacker News. Felicidades
r3st0r3
1
@ Eng.Encontrado aquí está. Ya no estaba en la primera plana, pero estaba allí ayer. news.ycombinator.net/item?id=3751901 (ayer fue a mediados del domingo aquí en India).
r3st0r3

Respuestas:

180

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 Listde HashMaps debería ser una Listde Maps, 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 Listde HashMaps no debería ser una Listde Maps.

la verdad
fuente
66
Aún así, HashMapse Mapdebe al polimorfismo.
Eng.Fouad
46
Correcto, pero una Listde HashMaps no es una Listde Maps.
trutheality
1
Esto se llama cuantificación limitada
Dan Burton
Buen ejemplo. También vale la pena señalar que incluso si declara 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)
Raze
@Raze no, en realidad se puede agregar una HashMapa una lista de Maps, ambos de sus ejemplos son legales, si yo los estoy leyendo correctamente.
verdad
24

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).

Tom Hawtin - tackline
fuente
1
puede explicarlo más? No puedo entender o algún enlace para una buena práctica?
Samir Mangroliya
3
@Samir ¿Explicar qué? List<String>no es un subtipo de List<Object>? - ver, por ejemplo, stackoverflow.com/questions/3246137/…
Tom Hawtin - línea de ataque
2
Esto no explica cuál es la diferencia entre con o sin ? extends. Tampoco explica la correlación con super / subtipos o co / contravarianza (si hay alguna).
Abel
16

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:

Covarianza

Si tienes una clase Automobile, entonces Cary Truckson 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

Contravarianza

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.

¿Cuándo usar contra o covarianza?

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? .

Entonces, ¿qué pasa con 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 TreeMaphereda 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 probablemente sea obvio, pero es posible que ya haya notado que usar la extendspalabra 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 extenden 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>());
Abel
fuente
Nada en "Entonces, ¿qué es entonces con ..." se compilará.
NobleUplift
@NobleUplift: es difícil ayudarlo si no proporciona el mensaje de error que recibe. Considere también, como alternativa, hacer una nueva pregunta sobre SO para obtener su respuesta, una mayor probabilidad de éxito. El código anterior son solo fragmentos, depende de que lo haya implementado en su escenario.
Abel
No tengo una nueva pregunta, tengo mejoras. 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.
NobleUplift
@NobleUplift: lo siento, viajando mucho tiempo, se solucionará cuando regrese, ¡y gracias por señalar el error evidente! :)
Abel
No hay problema, me alegro de que se solucione. Hice las ediciones por ti si quieres aprobarlas.
NobleUplift
4

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 Setde Aobjetos que originalmente escribí con esta firma:

void myMethod(Set<A> set)

Pero en realidad quiere llamarlo con Sets de subclases de A. ¡Pero esto no está permitido! (La razón de esto es que myMethodpodría agregar objetos setque sean de tipo A, pero no del subtipo que setse 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, setel 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.

Rörd
fuente
0

Como mencionó, podría haber dos versiones siguientes 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 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 Listpueda contener objetos de un tipo dado, se introdujeron los genéricos de Java ? extends. Entonces, en el n. ° 1, Listpuede contener cualquier objeto derivado del Map<String, String>tipo. Agregar cualquier otro tipo de datos arrojaría una excepción.

Funsuk Wangadu
fuente