[java] ¿Cuáles son las razones por las que Map.get (clave Object) no es (completamente) genérica?


Answers

Un increíble codificador de Java en Google, Kevin Bourrillion, escribió sobre este tema en una publicación de blog hace un tiempo (ciertamente en el contexto de Set lugar de Map ). La oración más relevante:

Uniformemente, los métodos de Java Collections Framework (y la Biblioteca de colecciones de Google también) 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 un principio - .NET parece estar bien requiriendo 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 cual no es un problema en .NET es que existe un problema mayor en .NET de varianza más limitada ...)

Question

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




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

Las verificaciones de igualdad se pueden realizar independientemente del 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 clave y las operaciones basadas en la equivalencia clave 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 tipo.




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 . Esta fue una falla de diseño temprana en las bibliotecas estándar de Java. Junto con las limitaciones en el sistema de tipos de Java, obliga a todo lo que dependa de 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 usar un sustituto genérico. Java funcional viene con clases de tipos solo para este propósito: Hash<A> e Equal<A> . Se proporciona un contenedor para HashMap<K, V> que toma Hash<K> y Equal<K> en su constructor. Los métodos get y contains esta clase 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



Compatibilidad.

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

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

Podrían haber introducido un método adicional , digamos get_checked (<K> o) y desaprobar el antiguo método get (), por lo que había una ruta de transición más suave. Pero por alguna razón, esto no fue hecho. (La situación en la que nos encontramos ahora es que debe instalar herramientas como findBugs para verificar la compatibilidad de tipos entre el argumento get () y el tipo de clave declarada <K> del mapa).

Los argumentos relacionados con la semántica de .equals () son falsos, creo. (Técnicamente están en lo cierto, pero sigo pensando que son falsas. Ningún diseñador en su sano juicio va a hacer que o1.equals (o2) sea verdadero si o1 y o2 no tienen ninguna superclase común).




Estaba viendo esto y pensando por qué lo hicieron de esta manera. No creo que ninguna de las respuestas existentes explique por qué no podrían 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 presentaron genéricos, NO crearon una nueva interfaz. La interfaz de Mapa es el mismo antiguo mapa no genérico, solo sirve como versión genérica y no genérica. De esta forma, si tiene un método que acepta Map no genérico, puede pasarle un Map<String, Customer> y aún así funcionar. Al mismo tiempo, el contrato de get acepta Object, por lo que la nueva interfaz también debería ser compatible con este contrato.

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




Related