Cuáles son las razones por las que Map.get (clave de objeto) no es (completamente) genérico


¿Cuáles son las razones detrás de la decisión de no tener un método get completamente 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).

Author: h0ussni, 2009-05-13

11 answers

Como han mencionado otros, la razón por la que 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 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 el mismo tipo que el objeto.

Aunque puede ser comúnmente cierto que muchas clases tienen equals() definido de modo que sus objetos sólo pueden ser iguales a 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 List son iguales si ambos son Listas y tienen el mismo contenido, incluso si son implementaciones diferentes de 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 mí llamar get() con un LinkedList como argumento, y debe recuperar la clave que es una lista con la misma contenido. Esto no sería posible si get() fuera genérico y restringiera su tipo de argumento.

 242
Author: newacct,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-08-27 14:04:59

Un genial programador de Java en Google, Kevin Bourrillion, escribió exactamente sobre este tema en una entrada de blog hace un tiempo (es cierto que en el contexto de Set en lugar de Map). La frase más relevante:

Uniformemente, los métodos de Java Collections Framework (y Google Colecciones Biblioteca también) nunca restringir los tipos de sus parámetros excepto cuando es necesario prevenir la colección de getting broken.

No estoy del todo seguro de estar de acuerdo con esto como 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 que no es un problema en.NET es que existe el mayor problema en. NET de varianza más limitada...)

 98
Author: Jon Skeet,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-05-13 11:42:15

El contrato se expresa así:

Más formalmente, si este mapa contiene un asignación de una clave k a un valor v tal that (key = = null ? k = = null : clave.es igual a(k) ), entonces este método devuelve v; de lo contrario devuelve null. (Puede haber a lo sumo uno de estos asignación.)

(mi énfasis)

Y como tal, una búsqueda de clave exitosa depende de la implementación del método equality en la clave de entrada. Que no es necesariamente dependiente de la clase de k.

 27
Author: Brian Agnew,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-05-13 11:34:45

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 comprobaciones 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 para la equivalencia de clave, y las operaciones basadas en la equivalencia de clave, aceptar cualquier tipo Object.

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

 16
Author: erickson,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-09-22 14:48:27

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 continuar apoyando el contrato original de la API. Considere de nuevo algunos ejemplos de Java.útil.Colección. La API pre-genérica se ve así:

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

Un intento ingenuo de generalizarlo es:

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

Si bien esto es seguro para el tipo, no está a la altura del original de la API contrato. El método containsAll () funciona con cualquier tipo de colección entrante. Sólo tenga éxito si la colección entrante realmente contiene solo instancias de E, pero:

  • El tipo estático del entrante colección podría diferir, tal vez porque la persona que llama no sabe tipo exacto de la colección que es pasado en, o tal vez porque es un Colección, donde S es a subtipo de E.
  • Es perfectamente legitimate to call containsAll () with una colección de un tipo diferente. El la rutina debe funcionar, devolviendo falso."
 11
Author: Yardena,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-05-14 14:16:56

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 fallo 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 dependa de equals y hashCode a tomar Object.

La única manera de tener tablas hash seguras de tipo e 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 fin: Hash<A> y Equal<A>. Una envoltura para HashMap<K, V> se proporciona que toma Hash<K> y Equal<K> en su constructor. Por lo tanto, los métodos get y contains de 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
 5
Author: Apocalisp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-05-28 02:16:24

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

Java tiene una construcción genérica polimórfica como <? extends SomeClass>. Marcado tal referencia puede apuntar a tipo firmado con <AnySubclassOfSomeClass>. Pero polymorphic generic hace que esa referencia sea readonly. El compilador le permite usar tipos genéricos solo como tipo de método de retorno (como getters simples), pero bloquea el uso de métodos donde el tipo genérico es argumento (como setters ordinarios). Significa que si escribes Map<? extends KeyType, ValueType>, el compilador no le permite llamar a method 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).

 4
Author: Owheee,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-10-20 18:29:57

Compatibilidad hacia Atrás, supongo. Map (o HashMap) todavía necesita soporte get(Object).

 1
Author: Anton Gogolev,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2009-05-13 11:33:02

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 hacer que la nueva interfaz genérica aceptara 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 viejo Mapa no genérico que solo sirve como versión genérica y no genérica. De esta manera, si tiene un método que acepta un mapa no genérico, puede pasarlo a Map<String, Customer> y todavía funcionaría. Al mismo tiempo, el contrato para get acepta Object, por lo que la nueva interfaz también debería admitir este contrato.

En mi opinión, deberían haber agregado una nueva interfaz e implementado ambas en la colección existente, pero 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 las interfaces no lo serían.

 1
Author: Stilgar,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-26 07:34:54

Compatibilidad.

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

Si hubieran cambiado este método para obtener( o) habría forzado potencialmente el mantenimiento masivo de código en los usuarios de java solo para hacer que el código de trabajo se compile de nuevo.

They could have introduced an additional method, say get_checked( o) and deprecate the old get() method so there was a gentler transition path. Pero por alguna razón, esto no se hizo. (Situación ahora estamos en que necesita instalar herramientas como FindBugs para verificar la compatibilidad de tipos entre el argumento get() y el tipo de clave declarado del mapa.)

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

 1
Author: Erwin Smout,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-04-01 08:47:37

Estamos haciendo una gran refactorización en este momento y nos faltaba este get() fuertemente escrito para comprobar que no nos perdimos algún get() con tipo antiguo.

Pero encontré workaround/ugly trick para la comprobación del tiempo de compilación: crear interfaz de mapa con get fuertemente escrito, containsKey, remove... y ponlo en Java.util paquete 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 eclipse kepler).

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

 0
Author: henva,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-02-10 06:20:05