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




6 Answers

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.

recorrer hashmap

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




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.




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



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




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.




Related