
WMR
¿Cuáles son las razones detrás de la decisión de no tener un método get totalmente genérico en la interfaz de java.util.Map<K, V>
.
Para aclarar la pregunta, 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
).

nuevacuenta
Como lo mencionaron otros, la razón por la cual get()
etc. no es genérico porque la clave de la entrada que está recuperando no tiene que ser del mismo tipo que el objeto al que pasa get()
; la especificación del método sólo exige que sean iguales. Esto se deduce de cómo el equals()
El método toma un objeto como parámetro, no solo del mismo tipo que el objeto.
Aunque puede ser comúnmente cierto que muchas clases tienen equals()
definido para que sus objetos solo puedan 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 Lista son iguales si ambos son Listas y tienen el mismo contenido, incluso si son implementaciones diferentes de List
. Entonces, 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 llame get()
con un LinkedList
como argumento, y debería recuperar la clave, que es una lista con el mismo contenido. Esto no sería posible si get()
eran genéricos y restringían su tipo de argumento.
Un increíble programador de Java en Google, Kevin Bourrillion, escribió exactamente sobre este problema en un entrada en el blog hace un tiempo (ciertamente en el contexto de Set
en lugar de Map
). La frase más relevante:
De manera uniforme, los métodos de Java Collections Framework (y también de Google Collections Library) nunca restringen los tipos de sus parámetros, excepto cuando es necesario para evitar que la colección se rompa.
No estoy completamente seguro de estar de acuerdo con él como principio: .NET parece estar bien al requerir el tipo de clave correcto, por ejemplo, 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 la más grande problema en .NET de varianza más limitada…)
El contrato se expresa así:
Más formalmente, si este mapa contiene un mapeo de una clave k a un valor v tal que (key==null ? k==null :
clave.igual(k)), entonces este método devuelve v; de lo contrario, devuelve nulo. (Puede haber como máximo una de esas asignaciones).
(mi énfasis)
y como tal, una búsqueda de clave exitosa depende de la implementación del método de igualdad de la clave de entrada. Eso no es necesariamente dependiente de la clase de k.

erickson
es una aplicación de ley de postel, “Sé conservador en lo que haces, sé liberal en lo que aceptas de los demás”.
Las comprobaciones de igualdad se pueden realizar independientemente del tipo; los equals
El método se define en el Object
clase y acepta cualquier Object
como parámetro. Por lo tanto, tiene sentido para la equivalencia de clave y las operaciones basadas en la equivalencia de clave aceptar cualquier Object
escribe.
Cuando un mapa devuelve valores clave, conserva toda la información de tipo posible mediante el uso del parámetro de tipo.
Creo que esta sección de Generics Tutorial explica la situación (mi énfasis):
“Debe asegurarse de que la API genérica no sea excesivamente restrictiva; debe seguir siendo compatible con el contrato original de la API. Considere nuevamente algunos ejemplos de java.util.Collection. La API pregené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 esto es seguro para escribir, no cumple con el contrato original de la API.
El método containsAll() 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
, donde S es un subtipo de E.
- Es perfectamente legítimo llamar a containsAll() con una colección de un tipo diferente. La rutina debería funcionar, devolviendo falso”.
La razón es que la contención está determinada por equals
y hashCode
cuáles son los métodos en Object
y ambos toman un Object
parámetro. 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 cualquier cosa que se base en equals y hashCode a tomar Object
.
La única forma de tener tablas hash de tipo seguro e igualdad en Java es evitar Object.equals
y Object.hashCode
y use un sustituto genérico. Java funcional viene con clases de tipos solo para este propósito: Hash<A>
y Equal<A>
. un envoltorio para HashMap<K, V>
se proporciona que toma Hash<K>
y Equal<K>
en su constructor. esta clase get
y contains
por lo tanto, los métodos 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

Sr. Lister
Compatibilidad.
Antes de que los genéricos estuvieran disponibles, solo había get (Objeto o).
Si hubieran cambiado este método para obtener ( o), potencialmente habría forzado el mantenimiento masivo del código en los usuarios de Java solo para hacer que el código de trabajo vuelva a compilarse.
Ellos pudo han introducido un adicional método, diga get_checked( o) y elimine el antiguo método get() para que haya una ruta de transición más suave. Pero por alguna razón, esto no se hizo. (La situación en la que nos encontramos ahora es que necesita instalar herramientas como findBugs para comprobar la compatibilidad de tipos entre el argumento get() y el tipo de clave declarada del mapa).
Creo que los argumentos relacionados con la semántica de .equals() son falsos. (Técnicamente son correctos, pero sigo pensando que son falsos. Ningún diseñador en su sano juicio hará que o1.equals(o2) sea verdadero si o1 y o2 no tienen ninguna superclase común).
Pregunta similar con respecto a la colección: stackoverflow.com/questions/104799/…
– AlikElzin-kilaka
4 de septiembre de 2012 a las 12:37
Posible duplicado de ¿Por qué las colecciones de Java no eliminan los métodos genéricos?
– Olé VV
26 de octubre de 2016 a las 4:45
Asombroso. Uso Java desde hace más de 20 años y hoy me doy cuenta de este problema.
– Gato fantasma
9 de agosto de 2018 a las 8:52