2009-01-15 23 views
16

Esta pregunta es impulsado por strange HashMap.put() behaviour¿Por qué Long.valueOf (0) .equals (Integer.valueOf (0)) false?

creo que entiendo por qué Map<K,V>.put toma un K pero Map<K,V>.get toma un Object, parece que no hacerlo se rompa demasiado código existente.

Ahora entramos en un escenario muy propenso a errores:

java.util.HashMap<Long, String> m = new java.util.HashMap<Long, String>(); 
m.put(5L,"Five"); // compiler barfs on m.put(5, "Five") 
m.contains(5); // no complains from compiler, but returns false 

¿No podía éste han sido resueltos mediante la devolución de cierto si el valor Long se withing int rango y los valores son iguales?

Respuesta

24

Aquí es la fuente de la Long.java

public boolean equals(Object obj) { 
    if (obj instanceof Long) { 
     return value == ((Long)obj).longValue(); 
    } 
    return false; 
} 

es decir, necesita ser un tipo largo para ser igual. Creo que la diferencia clave entre:

long l = 42L 
int i = 42; 
l == i 

y el ejemplo anterior es que a partir de primitivas se puede producir un ensanchamiento implícita del valor int, sin embargo, con los tipos de objetos que no hay reglas para la conversión implícita desde un entero a una larga.

También verifique Java Puzzlers, tiene muchos ejemplos similares a esto.

+1

Quizás no estaba claro. Sé por qué ocurre lo mismo que en el código fuente que hace que suceda de esa manera, había leído el código antes de publicarlo. Mi pregunta era por qué se decidió que debería ser así? –

+3

http://stackoverflow.com/questions/445990/why-is-long-valueof0-equalsinteger-valueof0-false#446911 lo explica de manera más adecuada. Ya que la comparación de Long to Integer para la igualdad puede violar la simetría. –

5

Sí, pero todo se reduce al algoritmo de comparación y qué tan lejos tomar las conversiones. Por ejemplo, ¿qué desea que suceda cuando prueba m.Contains("5")? ¿O si le pasas una matriz con 5 como primer elemento? Simplemente hablando, parece estar conectado "si los tipos son diferentes, las claves son diferentes".

Luego tome una colección con un object como la clave. ¿Qué desea que suceda si put a 5L, luego intente obtener 5, "5", ...? ¿Qué pasa si put a 5L y 5 y "5" y desea verificar 5F?

Dado que se trata de una colección genérica (o plantilla, o como se quiera llamar), debería verificar y hacer algunas comparaciones especiales para ciertos tipos de valores. Si K es int, verifique si el objeto pasado es long, short, float, double, ..., luego convierta y compare. Si K es float, luego verifique si el objeto pasado es ...

Usted entiende el punto.

Otra implementación podría haber sido lanzar una excepción si los tipos no coincidían, sin embargo, y con frecuencia deseo que sí lo haga.

+1

No entiendo el punto. Seguramente hay una diferencia entre un Long siendo igual a un Inter y una String siendo igual a un Entero? Hay una conversión implícita, por ejemplo. La excepción podría haber ayudado a detectar este caso, sí. –

4

Su pregunta parece razonable en sí misma, pero sería una violación de las convenciones generales para equals(), si no es su contrato, devolver verdadero para dos tipos diferentes.

0

Parte del diseño del lenguaje Java era para que los objetos nunca se convirtieran implícitamente a otros tipos, a diferencia de C++. Esto fue parte de hacer de Java un lenguaje pequeño y simple. Una parte razonable de la complejidad de C++ proviene de las conversiones implícitas y sus interacciones con otras características.

Además, Java tiene una dicotomía nítida y visible entre primitivos y objetos. Esto es diferente de otros idiomas donde esta diferencia se oculta bajo las cubiertas como una optimización. Esto significa que no puede esperar que Long e Integer actúen como long e int.

El código de la biblioteca se puede escribir para ocultar estas diferencias, pero eso puede hacer daño al hacer que el entorno de programación sea menos uniforme.

6

En general, aunque no está estrictamente expresado en el contrato para equals(), los objetos no deben considerarse iguales a otro objeto que no sea de la misma clase (incluso si es una subclase). Considere la propiedad simétrica: si a.equals (b) es verdadero, entonces b.equals (a) también debe ser verdadero.

Vamos a tener dos objetos, foo de la clase, y Superbar de clase Sub, que se extiende Super. Ahora considere la implementación de equals() en Super, específicamente cuando se llama como foo.equals(bar). Foo solo sabe que la barra está fuertemente tipeada como Object, por lo que para obtener una comparación precisa necesita verificar que sea una instancia de Super y si no devuelve falso. Lo es, así que esta parte está bien. Ahora compara todos los campos de instancia, etc. (o cualquiera que sea la implementación de comparación real) y los encuentra iguales. Hasta aquí todo bien.

Sin embargo, según el contrato, solo puede volver verdadero si sabe que bar.equals (foo) también va a ser verdadero. Como bar puede ser cualquier subclase de Super, no está claro si se va a anular el método equals() (y si es probable que lo esté). Por lo tanto, para asegurarse de que su implementación sea correcta, debe escribirla simétricamente y asegurarse de que los dos objetos sean de la misma clase.

Más fundamentalmente, los objetos de diferentes clases no pueden considerarse iguales, ya que en este caso, solo uno de ellos se puede insertar en un HashSet<Sub>, por ejemplo.

+0

Long y Entero son tipos de clase diferentes, y por lo tanto, compararlos usando iguales siempre devolverá falso. –

+2

Para Long l = 0 e Integer i = 0 si l.equals (i) puede ser verdadero, entonces i.equals (l) también puede ser cierto. La simetría se conserva. –

+10

@dtsazza, Steve Kuo: Estoy totalmente en desacuerdo. De hecho, en la biblioteca de Java hay lugares donde los objetos de diferentes clases se consideran iguales. Por ejemplo, la interfaz de la Lista especifica que las listas de las diferentes clases sean iguales si sus contenidos son iguales, independientemente de sus clases; así que una ArrayList puede ser .equals() para una LinkedList. – newacct

0

Así que el código debería ser ....

java.util.HashMap<Long, String> m = new java.util.HashMap<Long, String>(); 
m.put(5L, "Five"); // compiler barfs on m.put(5, "Five") 
System.out.println(m.containsKey(5L)); // true 

Están olvidando que Java se autoboxing su código, por lo que el código anterior podría ser equivelenet a

java.util.HashMap<Long, String> m = new java.util.HashMap<Long, String>(); 
m.put(new Long(5L), "Five"); // compiler barfs on m.put(5, "Five") 
System.out.println(m.containsKey(new Long(5))); // true 
System.out.println(m.containsKey(new Long(5L))); // true 

Así que una parte de su problema es el autoboxing. La otra parte es que tienes diferentes tipos como otros carteles han declarado.

+0

Entiendo autoboxing. Sospecho que no entendiste mi punto, que es que es fácil olvidar la L al llamar a containsKey. –

0

Las otras respuestas explican adecuadamente por qué falla, pero ninguna de ellas aborda cómo escribir código que es menos propenso a errores en este tema. Tener que recordar para agregar tipos-moldes (sin ayuda del compilador), sufijo primitivas con L y así sucesivamente no es aceptable en mi humilde opinión.

Recomiendo usar la biblioteca de colecciones GNU trove cuando tiene primitivas (y en muchos otros casos). Por ejemplo, hay un TLongLongHashMap que almacena cosas interalmente como longitudes primitivas. Como resultado, nunca se termina con el boxeo/unboxing, y nunca termina con comportamientos inesperados:

TLongLongHashMap map = new TLongLongHashMap(); 
map.put(1L, 45L); 
map.containsKey(1); // returns true, 1 gets promoted to long from int by compiler 
int x = map.get(1); // Helpful compiler error. x is not a long 
int x = (int)map.get(1); // OK. cast reassures compiler that you know 
long x = map.get(1); // Better. 

y así sucesivamente. No es necesario obtener el tipo correcto, y el compilador le da un error (que puede corregir o anular) si hace algo tonto (intente almacenar un largo en un int).

Las reglas de auto-casting significan que las comparaciones funcionan correctamente, así:

if(map.get(1) == 45) // 1 promoted to long, 45 promoted to long...all is well 

Como beneficio adicional, la sobrecarga de memoria y el rendimiento en tiempo de ejecución es mucho mejor.