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

12 minutos de lectura

¿Cuales son las razones por las que Mapget clave de
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).

  • 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

1646966355 895 ¿Cuales son las razones por las que Mapget clave de
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.

  • entonces porque es V Get(K k) C ª#?

    usuario166390

    14/09/2011 a las 15:21

  • La pregunta es si quieres llamar m.get(linkedList)por qué no definiste mes tipo como Map<List,Something>? No puedo pensar en un caso de uso donde llamar m.get(HappensToBeEqual) sin cambiar el Map type para obtener una interfaz tiene sentido.

    – Elazar Leibovich

    14 de febrero de 2012 a las 12:07

  • Vaya, grave defecto de diseño. Tampoco recibes una advertencia del compilador, jodido. Estoy de acuerdo con Elazar. Si esto es realmente útil, lo que dudo que suceda a menudo, getByEquals (tecla de objeto) suena más razonable …

    – mjs

    26 de septiembre de 2012 a las 12:25


  • Esta decisión parece que se tomó sobre la base de la pureza teórica en lugar de la practicidad. Para la mayoría de los usos, los desarrolladores preferirían ver el argumento limitado por el tipo de plantilla, que tenerlo ilimitado para admitir casos extremos como el mencionado por newacct en su respuesta. Dejar las firmas sin plantilla crea más problemas de los que resuelve.

    – Sam Goldberg

    17 dic 2013 a las 18:03


  • @newacct: “perfectamente seguro para escribir” es un reclamo sólido para una construcción que puede fallar de manera impredecible en tiempo de ejecución. No restrinja su vista a los mapas hash que funcionan con eso. TreeMap puede fallar cuando pasa objetos del tipo incorrecto al get pero puede pasar ocasionalmente, por ejemplo, cuando el mapa está vacío. Y lo que es peor, en caso de suministro Comparator los compare El método (¡que tiene una firma genérica!) podría llamarse con argumentos del tipo incorrecto sin ninguna advertencia sin marcar. Esta es comportamiento roto.

    – Holger

    20/10/2014 a las 18:43

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

  • Estoy seguro de que Josh Bloch ha escrito sobre eso en alguna parte. Un intento anterior utilizó el parámetro genérico para el parámetro, pero se encontró que era demasiado incómodo.

    – Tom Hawtin – tachuela

    13 de mayo de 2009 a las 12:19

  • Apocalipsis: eso no es cierto, la situación sigue siendo la misma.

    –Kevin Bourrillion

    4 de noviembre de 2009 a las 16:24

  • @ user102008 No, la publicación no está mal. Aunque un Integer y un Double nunca pueden ser iguales entre sí, todavía es una pregunta justa preguntar si un Set<? extends Number> contiene el valor new Integer(5).

    –Kevin Bourrillion

    19 de junio de 2012 a las 15:10

  • Nunca he querido verificar la membresía en un Set<? extends Foo>. Con mucha frecuencia cambié el tipo de clave de un mapa y luego me sentí frustrado porque el compilador no pudo encontrar todos los lugares donde el código necesitaba actualizarse. Realmente no estoy convencido de que esta sea la compensación correcta.

    – Porculus

    21/09/2012 a las 20:52

  • @EarthEngine: Siempre ha estado roto. Ese es el punto: el código está roto, pero el compilador no puede atraparlo.

    – Jon Skeet

    20 de marzo de 2013 a las 6:46

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.

  • También depende de hashCode(). Sin una implementación adecuada de hashCode(), un bien implementado equals() es bastante inútil en este caso.

    – rudolfson

    13 de mayo de 2009 a las 11:51

  • Supongo que, en principio, esto le permitiría usar un proxy ligero para una clave, si recrear la clave completa no fuera práctico, siempre que equals() y hashCode() estén implementados correctamente.

    – Bill Michell

    13 de mayo de 2009 a las 12:32

  • @rudolfson: Hasta donde yo sé, solo un HashMap depende del código hash para encontrar el cubo correcto. Un TreeMap, por ejemplo, utiliza un árbol de búsqueda binario y no se preocupa por hashCode().

    – robo

    13 de mayo de 2009 a las 17:19

  • Estrictamente hablando, get() no necesita tomar un argumento de tipo Object para satisfacer el contacto. Imagine que el método get estuviera restringido al tipo de clave K – el contrato seguiría siendo válido. Por supuesto, los usos en los que el tipo de tiempo de compilación no era una subclase de K ahora fallaría al compilar, pero eso no invalida el contrato, ya que los contratos discuten implícitamente qué sucede si el código se compila.

    – BeeOnRope

    2 de junio de 2016 a las 23:09

  • Cuando hablamos del contrato, especialmente en torno a key.equals(k)creo que es importante señalar también que el contrato además permite simplemente lanzar un ClassCastException es key es de un tipo incompatible. Esto reduce la utilidad de remove(Object) como una especie de removeIf(Predicate), porque depende de la implementación de la colección. Si dependemos del comportamiento de una implementación de colección en particular, entonces tal vez debería exponerse como un método de esa implementación en particular y la interfaz de colección debería tener un comportamiento consistente para todas las implementaciones.

    – Jesse

    1 de febrero a las 14:07

¿Cuales son las razones por las que Mapget clave de
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”.

  • Por qué no containsAll( Collection< ? extends E > c )¿luego?

    – Juez Mental

    29/04/2013 a las 23:20

  • @JudgeMental, aunque no se da como ejemplo anterior, también es necesario permitir containsAll con un Collection<S> donde S es un supertipo de E. Esto no estaría permitido si fuera containsAll( Collection< ? extends E > c ). Además, como es explícitamente indicado en el ejemplo, es legítimo pasar una colección de un tipo diferente (con el valor devuelto entonces siendo false).

    – davmac

    10 de junio de 2014 a las 12:13


  • No debería ser necesario permitir containsAll con una colección de un supertipo de E. Argumento que es necesario no permitir esa llamada con una verificación de tipo estático para evitar un error. Es un contrato tonto, que creo que es el punto de la pregunta original.

    – Juez Mental

    10 de junio de 2014 a las 15:51


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

  • Por qué no containsAll( Collection< ? extends E > c )¿luego?

    – Juez Mental

    29/04/2013 a las 23:20

  • @JudgeMental, aunque no se da como ejemplo anterior, también es necesario permitir containsAll con un Collection<S> donde S es un supertipo de E. Esto no estaría permitido si fuera containsAll( Collection< ? extends E > c ). Además, como es explícitamente indicado en el ejemplo, es legítimo pasar una colección de un tipo diferente (con el valor devuelto entonces siendo false).

    – davmac

    10 de junio de 2014 a las 12:13


  • No debería ser necesario permitir containsAll con una colección de un supertipo de E. Argumento que es necesario no permitir esa llamada con una verificación de tipo estático para evitar un error. Es un contrato tonto, que creo que es el punto de la pregunta original.

    – Juez Mental

    10 de junio de 2014 a las 15:51


1646966356 13 ¿Cuales son las razones por las que Mapget clave de
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).

  • Pero allí también el método put (clave de objeto, valor de objeto) se ha cambiado a put (clave K, valor V), ¡y no hay problema con eso!

    –Manuel Romeiro

    14 de julio de 2020 a las 16:50

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad