¿Por qué Collection.remove (Object o) no es genérico?
Parece que Collection<E>
podría haberboolean remove(E o);
Luego, cuando accidentalmente intente eliminar (por ejemplo) en Set<String>
lugar de cada Cadena individual de una Collection<String>
, sería un error de tiempo de compilación en lugar de un problema de depuración posterior.
java
api
generics
collections
Chris Mazzola
fuente
fuente
Respuestas:
Josh Bloch y Bill Pugh se refieren a este tema en Java Puzzlers IV: The Phantom Reference Menace, Attack of the Clone y Revenge of The Shift .
Josh Bloch dice (6:41) que intentaron generar el método get de Map, remove method y algunos otros, pero "simplemente no funcionó".
Hay demasiados programas razonables que no podrían generarse si solo permite el tipo genérico de la colección como tipo de parámetro. El ejemplo dado por él es una intersección de a
List
deNumber
sy aList
deLong
s.fuente
remove()
(Map
tanto como enCollection
) no es genérico porque debería poder pasar cualquier tipo de objeto aremove()
. El objeto eliminado no tiene que ser del mismo tipo que el objeto al que se pasaremove()
; solo requiere que sean iguales. De la especificación deremove()
,remove(o)
elimina el objetoe
tal como(o==null ? e==null : o.equals(e))
estrue
. Tenga en cuenta que no hay nada que requierao
ye
sea del mismo tipo. Esto se deduce del hecho de que elequals()
método toma unObject
parámetro as, no solo el mismo tipo que el objeto.Aunque, puede ser comúnmente cierto que muchas clases se han
equals()
definido para que sus objetos solo puedan ser iguales a los objetos de su propia clase, ese no es siempre el caso. Por ejemplo, la especificación deList.equals()
dice que dos objetos List son iguales si ambas son Listas y tienen el mismo contenido, incluso si son implementaciones diferentes deList
. Volviendo al ejemplo en esta pregunta, es posible tener unMap<ArrayList, Something>
y para que llameremove()
con unLinkedList
argumento como, y debería eliminar la clave, que es una lista con el mismo contenido. Esto no sería posible siremove()
fuera genérico y restringiera su tipo de argumento.fuente
equals()
método. Podría ver más beneficios para escribir la seguridad en lugar de este enfoque "libertario". Creo que la mayoría de los casos con implementación actual son para errores que entran en nuestro código en lugar de alegría con esta flexibilidad queremove()
brinda el método.equals()
método"?T
debe declararse como un parámetro de tipo en la clase yObject
no tiene ningún parámetro de tipo. No hay forma de tener un tipo que se refiera a "la clase declarante".Equality<T>
conequals(T other)
. Entonces podrías tenerremove(Equality<T> o)
yo
es solo un objeto que se puede comparar con otroT
.Porque si su parámetro de tipo es un comodín, no puede usar un método genérico de eliminación.
Creo recordar haber encontrado esta pregunta con el método get (Object) de Map. El método get en este caso no es genérico, aunque debería esperar razonablemente que se le pase un objeto del mismo tipo que el primer parámetro de tipo. Me di cuenta de que si estás pasando Mapas con un comodín como primer parámetro de tipo, entonces no hay forma de sacar un elemento del Mapa con ese método, si ese argumento es genérico. Los argumentos comodín no pueden satisfacerse realmente, porque el compilador no puede garantizar que el tipo sea correcto. Especulo que la razón por la que agregar es genérico es que se espera que garantices que el tipo es correcto antes de agregarlo a la colección. Sin embargo, al eliminar un objeto, si el tipo es incorrecto, no coincidirá con nada de todos modos.
Probablemente no lo expliqué muy bien, pero me parece bastante lógico.
fuente
Además de las otras respuestas, hay otra razón por la cual el método debe aceptar un
Object
, que es predicados. Considere la siguiente muestra:El punto es que el objeto que se pasa al
remove
método es responsable de definir elequals
método. Construir predicados se vuelve muy simple de esta manera.fuente
yourObject.equals(developer)
, como se documenta en la API de Colecciones: java.sun.com/javase/6/docs/api/java/util/…equals
método, es decir, la simetría. El método remove solo está sujeto a su especificación siempre que sus objetos cumplan con la especificación equals / hashCode, por lo que cualquier implementación sería libre de hacer la comparación al revés. Además, su objeto predicado no implementa el.hashCode()
método (no puede implementarse de manera consistente a iguales), por lo tanto, la llamada a eliminar nunca funcionará en una colección basada en Hash (como HashSet o HashMap.keys ()). Que funcione con ArrayList es pura suerte.Collection.remove
, sin romper su contrato (si el pedido es consistente con iguales). Y una variada ArrayList (o AbstractCollection, creo) con la llamada igual activada aún implementaría correctamente el contrato: es su culpa si no funciona como se esperaba, ya que está rompiendo elequals
contrato.Suponga que uno tiene una colección de
Cat
, y algunas referencias a objetos de tiposAnimal
,Cat
,SiameseCat
, yDog
. Preguntar a la colección si contiene el objeto al que hace referencia la referenciaCat
oSiameseCat
parece razonable. Preguntar si contiene el objeto al que haceAnimal
referencia la referencia puede parecer dudoso, pero sigue siendo perfectamente razonable. El objeto en cuestión podría, después de todo, ser unCat
y podría aparecer en la colección.Además, incluso si el objeto
Cat
no es un objeto , no hay problema para decir si aparece en la colección, simplemente responda "no, no lo hace". Una colección de "tipo de búsqueda" de algún tipo debería poder aceptar de manera significativa la referencia de cualquier supertipo y determinar si el objeto existe dentro de la colección. Si la referencia de objeto pasada es de un tipo no relacionado, no hay forma de que la colección pueda contenerla, por lo que la consulta no es significativa en algún sentido (siempre responderá "no"). No obstante, dado que no hay forma de restringir los parámetros a subtipos o supertipos, lo más práctico es simplemente aceptar cualquier tipo y responder "no" a cualquier objeto cuyo tipo no esté relacionado con el de la colección.fuente
Comparable
se parametriza para los tipos con los que se puede comparar). Entonces no sería razonable permitir que las personas pasen algo de un tipo no relacionado.A
yB
de un tipo,X
yY
de otro, de modo queA
>B
yX
>Y
. Ya seaA
>Y
yY
<A
, oX
>B
yB
<X
. Esas relaciones solo pueden existir si las comparaciones de magnitud conocen ambos tipos. Por el contrario, el método de comparación de igualdad de un objeto puede simplemente declararse desigual a cualquier cosa de cualquier otro tipo, sin tener que saber nada sobre el otro tipo en cuestión. Un objeto de tipoCat
puede no tener idea de si es ...FordMustang
, pero no debería tener dificultad para decir si es igual a dicho objeto (la respuesta, obviamente, es "no").Siempre pensé que esto se debía a que remove () no tiene ninguna razón para importarle qué tipo de objeto le das. Sin embargo, es bastante fácil verificar si ese objeto es uno de los que contiene la Colección, ya que puede llamar a equals () en cualquier cosa. Es necesario verificar el tipo en add () para asegurarse de que solo contenga objetos de ese tipo.
fuente
Fue un compromiso. Ambos enfoques tienen su ventaja:
remove(Object o)
remove(E e)
brinda más seguridad de tipografía a lo que la mayoría de los programas quieren hacer al detectar errores sutiles en el momento de la compilación, como tratar de eliminar por error un número entero de una lista de cortos.La compatibilidad con versiones anteriores siempre fue un objetivo importante al desarrollar la API de Java, por lo tanto, se eligió eliminar (Objeto o) porque facilitaba la generación de código existente. Si la compatibilidad con versiones anteriores NO hubiera sido un problema, supongo que los diseñadores habrían elegido eliminar (E e).
fuente
Eliminar no es un método genérico, por lo que el código existente que utiliza una colección no genérica seguirá compilando y seguirá teniendo el mismo comportamiento.
Consulte http://www.ibm.com/developerworks/java/library/j-jtp01255.html para obtener más detalles.
Editar: Un comentarista pregunta por qué el método de agregar es genérico. [... eliminó mi explicación ...] El segundo comentarista respondió la pregunta de firebird84 mucho mejor que yo.
fuente
Otra razón es por las interfaces. Aquí hay un ejemplo para mostrarlo:
fuente
remove()
no es covariante. La pregunta, sin embargo, es si esto debería permitirse.ArrayList#remove()
funciona a modo de igualdad de valor, no de igualdad de referencia. ¿Por qué esperarías que aB
fuera igual a anA
? En su ejemplo, puede ser, pero es una expectativa extraña. Prefiero verte proporcionar unaMyClass
discusión aquí.Porque rompería el código existente (anterior a Java5). p.ej,
Ahora puede decir que el código anterior es incorrecto, pero suponga que o proviene de un conjunto heterogéneo de objetos (es decir, que contiene cadenas, números, objetos, etc.). Desea eliminar todas las coincidencias, lo cual era legal porque eliminar simplemente ignoraría las no cadenas porque no eran iguales. Pero si lo haces eliminar (String o), eso ya no funciona.
fuente