java - ¿Por qué no puede el compilador/JVM simplemente hacer que el autoboxing "simplemente funcione"?
integer caching (6)
¿Pueden imaginarse qué tan malo sería el rendimiento si cada Integer
llevara sobrecarga para el internamiento? Tampoco funciona para el new Integer
.
El lenguaje Java (no es un problema de JVM) no siempre puede ser unbox automático porque el código diseñado para Java anterior a la 1.5 todavía debe funcionar.
Autoboxing es bastante aterrador. Si bien entiendo completamente la diferencia entre ==
y .equals
no puedo dejar de ayudar a que el siguiente error me salga de la .equals
:
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));
Eso imprime
true
false
¿Por qué lo hicieron 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 verdadero sería mucho mejor.
EDITAR
No estoy de acuerdo con la rotura del código anterior. Al hacer que foo.get(0) == bar.get(0)
devuelva verdadero, ya rompiste el código.
¿No se puede resolver esto en el nivel de compilador reemplazando entero con int en código de bytes (siempre que nunca se le asigne nulo)
Cuando escribes
foo.get(0)
el compilador no importa cómo haya creado la lista. Solo mira el tipo de tiempo de compilación de la Lista foo. Entonces, si eso es un List <Integer>, tratará eso como una Lista <Integer>, como se supone que tiene que hacer, y una Lista <Integer> ''s get () siempre devuelve un Entero. Si quiere usar el ==, entonces 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.
Mucha gente tiene problemas con este problema, incluso personas que escriben libros sobre Java.
En Pro Java Programming , apenas unos centímetros más abajo el autor habla sobre problemas con el uso de enteros autoentrada como una clave en IdentityHashMap, usa claves enteras en cajas automáticas 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).
Si omite por completo el autoboxing, aún obtiene 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
Sea más explícito si quiere 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 la que Eclipse tiene autoboxing como advertencia por defecto.
Integer
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 de aquí
El resultado de toda esta magia es que puedes ignorar la distinción entre int y entero, 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. Finalmente, existen costos de rendimiento asociados con el boxeo y el desempaquetado, incluso si se realiza automáticamente
- ¿Por qué lo hicieron de esta manera?
Cada 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 memoria serían enormes.
- ¿Por qué la JVM no siempre se separa automáticamente en primitiva?
Porque la JVM no puede saber lo que quería. 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 llamadas a ==, este problema se volverá 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 por esto, solo recuerde esta regla:
NUNCA compare objetos con == a menos que tenga la intención de compararlos por sus referencias. Si haces eso, no puedo pensar en un escenario en el que te encuentres con un problema.