2010-04-08 18 views
11

Autoboxing es bastante aterrador. Aunque comprendo plenamente la diferencia entre == y .equals no puedo ayudar pero tiene el error de seguimiento a la mierda de mí:¿Por qué no puede el compilador/JVM simplemente hacer que el autoboxing "simplemente funcione"?

final List<Integer> foo = Arrays.asList(1, 1000); 
    final List<Integer> bar = Arrays.asList(1, 1000); 
    System.out.println(foo.get(0) == bar.get(0)); 
    System.out.println(foo.get(1) == bar.get(1)); 

que imprime

true 
false 

¿Por qué hacerlo de esta manera? Tiene algo que ver con enteros en caché, pero si ese es el caso, ¿por qué no almacenan en caché todos los enteros utilizados por el programa? ¿O por qué la JVM no siempre se descodifica automáticamente a primitiva?

Imprimir falso falso o cierto verdadero hubiera sido mucho mejor.

EDITAR

que no están de acuerdo acerca de la rotura de código antiguo. Al tener foo.get(0) == bar.get(0) return true, ya rompió el código.

¿Esto no puede ser resuelto a nivel del compilador mediante la sustitución de enteros con int en el código de bytes (siempre que no se asigna null)

+8

¡simplemente funciona! no de la manera esperada;) – OscarRyz

+0

En realidad, su ejemplo tiene poco que ver con el autoboxing, ese comportamiento es anterior a él. Es cierto que el autoboxing nos obliga a ser más conscientes de él: equals() es la forma (por lo general correcta) de comparar objetos, == es la (única) forma de comparar primitivas ... autoboxing ayuda al programador a tratar Integer e int (casi) indistintamente ... y de ahí el peligro de errores aquí. – leonbloy

+0

Esto puede ser debido a las peculiaridades que también afectan a las instancias de String. Por lo que yo entiendo, hay una agrupación que tiene lugar detrás de las escenas de acuerdo a su valor.Esto se puede prevenir utilizando la palabra clave 'new'. –

Respuesta

9
  • ¿Por qué hacerlo de esta manera?

Cada número entero entre -128 y 127 está guardado en caché por java. Lo hicieron, supuestamente, por el beneficio de rendimiento. Incluso si quisieran volver a tomar esta decisión ahora, es poco probable que lo hagan. Si alguien construyera un código según esto, su código se rompería cuando se lo quitara. Para la codificación de hobby, esto quizás no importe, pero para el código de la empresa, la gente se molesta y se producen demandas.

  • ¿Por qué no simplemente almacenan en caché todos los enteros utilizados por el programa?

No se pueden almacenar en caché todos los enteros, porque las implicaciones de la memoria serían enormes.

  • ¿Por qué la JVM no siempre es automática para primitiva?

Porque la JVM no puede saber lo que quiere. Además, este cambio podría romper fácilmente el código heredado no creado para manejar este caso.

Si la JVM se desempaqueta automáticamente a las primitivas en las llamadas a ==, este problema se volverá realmente MÁS confuso. Ahora debe recordar que == siempre compara referencias de objetos, a menos que los Objetos puedan ser desempaquetados. Esto causaría más casos extraños y confusos como el que mencionaste anteriormente.

En lugar de preocuparse demasiado sobre esto, sólo recuerda esta regla en su lugar:

NUNCA comparar objetos con == menos que vaya a ser la comparación de ellos por sus referencias. Si haces eso, no puedo pensar en un escenario en el que te encuentres con un problema.

+0

Nunca diga ** Nunca **. ** Debes ** comparar los valores de 'enum' con ==. –

+0

@Alexander Pogrebnyak - Tiene razón, y es por eso que agregué la cláusula "a menos que tenga la intención de compararlos por sus referencias". Esto cubre enums. Estoy a la altura de ** nunca ** :) –

+2

Punto justo. Entonces todavía tienes tu "Licencia para matar -9" :). 007 out –

7

¿Se imagina lo mal desempeño sería si cada Integer lleva a gastos generales para ¿internación? Tampoco funciona para new Integer.

El lenguaje Java (no es un problema de JVM) no siempre puede desunir automáticamente porque el código diseñado para Java anterior a la 1.5 aún debería funcionar.

+0

buena mención del código pre-1.5. +1 – Bozho

+0

otro +1 por mencionar el problema de compatibilidad con versiones anteriores – Chris

+0

ya rompieron el código anterior al almacenar en caché -128 a 127 – Pyrolistical

5

Integer s en el rango de bytes son el mismo objeto, porque están en caché. Integer s fuera del rango de bytes no lo son. Si todos los enteros fueran almacenados en caché, imagine la memoria requerida.

Y desde here

El resultado de todo esto es que la magia puede pasar por alto en gran medida la distinción entre int y Integer, con algunas advertencias. Una expresión entera puede tener un valor nulo. Si su programa intenta autounbox null, lanzará una NullPointerException. El operador == realiza comparaciones de identidad de referencia en expresiones de enteros y comparaciones de valores de igualdad en expresiones int. Por último, hay costes de funcionamiento asociados con el boxeo y unboxing, incluso si se hace de forma automática

+0

'Integer's fuera de [-128, 127] puede o no ser internado. (Y creo que la pregunta original comprende lo que está sucediendo, pero quiere saber por qué?) –

+0

Creo que no están con las implementaciones de Sun. De todos modos, se trata de almacenarlos en caché y los recursos necesarios para el almacenamiento en caché. – Bozho

4

Si omite por completo el autoboxing, sigue teniendo este comportamiento.

final List<Integer> foo = 
    Arrays.asList(Integer.valueOf(1), Integer.valueOf(1000)); 
final List<Integer> bar = 
    Arrays.asList(Integer.valueOf(1), Integer.valueOf(1000)); 

System.out.println(foo.get(0) == bar.get(0)); // true 
System.out.println(foo.get(1) == bar.get(1)); // false 

ser más explícita si desea un comportamiento específico:

final List<Integer> foo = 
    Arrays.asList(new Integer(1), new Integer(1000)); 
final List<Integer> bar = 
    Arrays.asList(new Integer(1), new Integer(1000)); 

System.out.println(foo.get(0) == bar.get(0)); // false 
System.out.println(foo.get(1) == bar.get(1)); // false 

Esta es una razón, ¿por qué Eclipse ha autoboxing como una advertencia por defecto.

+0

No está activado por defecto en mi copia de Eclipse, y no lo he cambiado. Qué versión estás usando? Revisé 3.2 y 3.4. – Chris

+0

@Crhis: Eclipse Gallileo (3.5) Ventana-> Preferencias Java-> Compilador-> Errores/Advertencias-> Posibles problemas de programación-> Conversiones de boxeo y desempaquetado. Tal vez no está activado por defecto, pero lo tenía activado desde que cambié a Java 5. –

+0

Definitivamente no está activado por defecto. Lo acabo de cambiar cuando lo leí aquí. –

3

Mucha gente tiene problemas con este problema, incluso personas que escriben libros sobre Java.

En apenas Pro Java Programming, el autor habla de problemas con el uso de enteros autoentrada como una clave en IdentityHashMap, utiliza claves de enteros autoenmarcadas en un WeakHashMap. Los valores de ejemplo que utiliza son mayores que 128, por lo que su llamada a la recolección de basura tiene éxito. Si alguien fuera a usar su ejemplo y usara valores menores a 128, su ejemplo fallaría (debido a que la clave está almacenada en caché permanente).

2

Cuando se escribe

foo.get(0) 

el compilador no importa cómo se creó la lista. Solo mira el tipo de tiempo de compilación de la Lista foo. Entonces, si eso es una lista < Integer>, tratará eso como una lista < Integer>, como se supone que tiene que hacer, y una lista < Entero> 's get() siempre devuelve un entero. Si desea utilizar el == entonces usted tiene que escribir

System.out.println(foo.get(0).intValue() == bar.get(0).intValue()); 

no

System.out.println(foo.get(0) == bar.get(0)); 

porque eso tiene un significado totalmente diferente.

Cuestiones relacionadas