recorrer example entre ejemplo diferencia java generics collections map

example - recorrer hashmap java



¿Cuáles son las razones por las que Map.get(Clave de objeto) no es(totalmente) genérico? (11)

Como lo mencionaron otros, la razón por la cual get() , etc. no es genérica porque la clave de la entrada que está recuperando no tiene que ser del mismo tipo que el objeto que pasa a get() ; La especificación del método solo requiere que sean iguales. Esto se deduce de cómo el método equals() toma un objeto como parámetro, no solo del mismo tipo que el objeto.

Aunque en general es cierto que muchas clases tienen equals() definido de modo que sus objetos solo pueden ser iguales a los objetos de su propia clase, hay muchos lugares en Java donde este no es el caso. Por ejemplo, la especificación para List.equals() dice que dos objetos de lista son iguales si ambos son listas y tienen el mismo contenido, incluso si son implementaciones diferentes de la List . Así que volviendo al ejemplo en esta pregunta, de acuerdo con la especificación del método es posible tener un Map<ArrayList, Something> y para que yo llame a get() con un LinkedList como argumento, y debería recuperar la clave que es Una lista con los mismos contenidos. Esto no sería posible si get() fuera genérico y restringiera su tipo de argumento.

¿Cuáles son las razones detrás de la decisión de no tener un método de obtención totalmente genérico en la interfaz de java.util.Map<K, V> .

Para aclarar la cuestión, la firma del método es

V get(Object key)

en lugar de

V get(K key)

y me pregunto por qué (lo mismo para remove, containsKey, containsValue ).


Compatibilidad hacia atrás, supongo. Map (o HashMap ) aún necesita soportar get(Object) .


Compatibilidad.

Antes de que estuvieran disponibles los genéricos, solo se obtenía (Objeto o).

Si hubieran cambiado este método para obtener (<K> o), potencialmente habría forzado el mantenimiento masivo de código en los usuarios de Java solo para que el código de trabajo se compile nuevamente.

Podrían haber introducido un método adicional , digamos get_checked (<K> o) y dejar de usar el antiguo método get () para que hubiera una ruta de transición más suave. Pero por alguna razón, esto no se hizo. (La situación en la que estamos ahora es que necesita instalar herramientas como findBugs para verificar la compatibilidad de tipos entre el argumento get () y el tipo de clave declarado <K> del mapa).

Los argumentos relacionados con la semántica de .equals () son falsos, creo. (Técnicamente son correctos, pero sigo creyendo que son falsos. Ningún diseñador en su sano juicio logrará que o1.equals (o2) sea verdadero si o1 y o2 no tienen una superclase común).


El contrato se expresa así:

Más formalmente, si este mapa contiene una asignación de una clave k a un valor v tal que (clave == null? K == null: key.equals (k) ), este método devuelve v; De lo contrario, devuelve nulo. (Puede haber como máximo un mapeo de este tipo).

(mi énfasis)

y como tal, una búsqueda de clave exitosa depende de la implementación de la clave de entrada del método de igualdad. Eso no es necesariamente dependiente de la clase de k.


Es una aplicación de la Ley de Postel, "sé conservador en lo que haces, sé liberal en lo que aceptas de los demás".

Se pueden realizar verificaciones de igualdad sin importar el tipo; El método equals se define en la clase Object y acepta cualquier Object como parámetro. Por lo tanto, tiene sentido que la equivalencia de claves y las operaciones basadas en la equivalencia de claves acepten cualquier tipo de Object .

Cuando un mapa devuelve valores clave, conserva tanta información de tipo como puede, mediante el uso del parámetro type.


Estaba mirando esto y pensando por qué lo hicieron de esta manera. No creo que ninguna de las respuestas existentes explique por qué no pudieron simplemente hacer que la nueva interfaz genérica acepte solo el tipo adecuado para la clave. La razón real es que a pesar de que introdujeron genéricos, NO crearon una nueva interfaz. La interfaz del Mapa es el mismo Mapa antiguo no genérico que sirve como versión tanto genérica como no genérica. De esta manera, si tiene un método que acepta un mapa no genérico, puede pasarlo a un Map<String, Customer> y aún funcionará. Al mismo tiempo, el contrato para obtener acepta Object, por lo que la nueva interfaz también debe ser compatible con este contrato.

En mi opinión, deberían haber agregado una nueva interfaz e implementado ambas en la colección existente, pero se decidieron a favor de interfaces compatibles, incluso si eso significa un diseño peor para el método get. Tenga en cuenta que las colecciones en sí serían compatibles con los métodos existentes, solo que las interfaces no lo serían.


Estamos haciendo una gran refactorización ahora y nos faltaba este get () fuertemente tipado para comprobar que no nos perdimos algunos get () con el tipo antiguo.

Pero encontré una solución alternativa / un truco feo para la verificación del tiempo de compilación: cree la interfaz de Map con get tipificado, contieneKey, eliminar ... y póngalo en el paquete java.util de su proyecto.

Obtendrá errores de compilación solo por llamar a get (), ... con tipos incorrectos, todo lo demás parece correcto para el compilador (al menos dentro de kepler de Eclipse).

No olvide eliminar esta interfaz después de verificar su compilación, ya que esto no es lo que desea en tiempo de ejecución.


Hay una razón más de peso, no se puede hacer técnicamente, porque rompe el mapa.

Java tiene una construcción genérica polimórfica como <? extends SomeClass> <? extends SomeClass> . Marcado dicha referencia puede apuntar al tipo firmado con <AnySubclassOfSomeClass> . Pero el genérico polimórfico hace que esa referencia sea de solo lectura . El compilador le permite usar tipos genéricos solo como tipo de método de retorno (como simples captadores), pero bloquea el uso de métodos donde el tipo genérico es un argumento (como establecedores normales). Significa que si escribes Map<? extends KeyType, ValueType> Map<? extends KeyType, ValueType> , el compilador no le permite llamar al método get(<? extends KeyType>) , y el mapa será inútil. La única solución es hacer que este método no sea genérico: get(Object) .


Kevin Bourrillion, un asombroso programador de Java en Google, escribió sobre este problema exactamente en una publicación de blog hace un tiempo (sin duda en el contexto de Set lugar de Map ). La frase más relevante:

De manera uniforme, los métodos del Java Collections Framework (y también la Biblioteca de Google Collections) nunca restringen los tipos de sus parámetros, excepto cuando es necesario para evitar que la colección se rompa.

No estoy del todo seguro de estar de acuerdo con esto como principio, por ejemplo .NET parece estar bien y requiere el tipo de clave correcto, pero vale la pena seguir el razonamiento en la publicación del blog. (Habiendo mencionado .NET, vale la pena explicar que parte de la razón por la que no es un problema en .NET es que existe el problema más grande en .NET de una varianza más limitada ...)


La razón es que la contención está determinada por equals y hashCode que son métodos en Object y ambos toman un parámetro Object . Este fue un defecto de diseño temprano en las bibliotecas estándar de Java. Junto con las limitaciones en el sistema de tipos de Java, obliga a todo lo que se basa en iguales y hashCode a tomar Object .

La única forma de tener tablas hash seguras para los tipos y la igualdad en Java es evitar Object.equals y Object.hashCode y usar un sustituto genérico. Java funcional viene con clases de tipo para este propósito: Hash<A> e Equal<A> . Se proporciona una envoltura para HashMap<K, V> que toma Hash<K> e Equal<K> en su constructor. Los métodos get y contains esta clase, por lo tanto, toman un argumento genérico de tipo K

Ejemplo:

HashMap<String, Integer> h = new HashMap<String, Integer>(Equal.stringEqual, Hash.stringHash); h.add("one", 1); h.get("one"); // All good h.get(Integer.valueOf(1)); // Compiler error


Creo que esta sección del Tutorial de genéricos explica la situación (mi énfasis):

"Debe asegurarse de que la API genérica no sea excesivamente restrictiva; debe continuar respaldando el contrato original de la API. Considere nuevamente algunos ejemplos de java.util.Collection. La API pre-genérica se ve así:

interface Collection { public boolean containsAll(Collection c); ... }

Un intento ingenuo de generarlo es:

interface Collection<E> { public boolean containsAll(Collection<E> c); ... }

Si bien este tipo es seguro, no cumple con el contrato original de la API. El método includesAll () funciona con cualquier tipo de colección entrante. Solo tendrá éxito si la colección entrante realmente contiene solo instancias de E, pero:

  • El tipo estático de la colección entrante puede diferir, quizás porque la persona que llama no conoce el tipo exacto de la colección que se está pasando, o quizás porque es una Colección <S>, donde S es un subtipo de E.
  • Es perfectamente legítimo llamar a contieneAll () con una colección de un tipo diferente. La rutina debería funcionar, devolviendo falso ".