Boxeo de enteros extraños en Java

114

Acabo de ver un código similar a este:

public class Scratch
{
    public static void main(String[] args)
    {
        Integer a = 1000, b = 1000;
        System.out.println(a == b);

        Integer c = 100, d = 100;
        System.out.println(c == d);
    }
}

Cuando se ejecuta, este bloque de código se imprimirá:

false
true

Entiendo por qué el primero es false: porque los dos objetos son objetos separados, por lo que ==compara las referencias. Pero no puedo entender, ¿por qué regresa la segunda declaración true? ¿Existe alguna extraña regla de autoboxing que se activa cuando el valor de un entero está en un cierto rango? ¿Que está pasando aqui?

Joel
fuente
3
@RC - No es un engaño, pero se discute una situación similar. Sin embargo, gracias por la referencia.
Joel
2
esto es horrible. es por eso que nunca entendí el sentido de todo ese primitivo, pero objeto, pero ambos, pero en caja automática, pero depende, pero aaaaaaaaargh.
njzk2
1
@Razib: La palabra "autoboxing" no es un código, así que no lo formatee así.
Tom

Respuestas:

102

La truelínea está realmente garantizada por la especificación del idioma. De la sección 5.1.7 :

Si el valor p que se encuadra es verdadero, falso, un byte, un carácter en el rango \ u0000 a \ u007f, o un número int o corto entre -128 y 127, entonces sean r1 y r2 los resultados de dos conversiones de boxeo cualesquiera de p. Siempre es el caso de que r1 == r2.

La discusión continúa, sugiriendo que aunque su segunda línea de salida está garantizada, la primera no lo está (vea el último párrafo citado a continuación):

Idealmente, encuadrar un valor primitivo p dado siempre produciría una referencia idéntica. En la práctica, esto puede no ser factible utilizando las técnicas de implementación existentes. Las reglas anteriores son un compromiso pragmático. La cláusula final anterior requiere que ciertos valores comunes siempre estén encuadrados en objetos indistinguibles. La implementación puede almacenarlos en caché, de manera perezosa o ansiosa.

Para otros valores, esta formulación no permite suposiciones sobre la identidad de los valores encuadrados por parte del programador. Esto permitiría (pero no requeriría) compartir algunas o todas estas referencias.

Esto asegura que en la mayoría de los casos, el comportamiento será el deseado, sin imponer una penalización indebida en el rendimiento, especialmente en dispositivos pequeños. Las implementaciones con menos memoria limitada pueden, por ejemplo, almacenar en caché todos los caracteres y cortos, así como enteros y largos en el rango de -32K - + 32K.

Jon Skeet
fuente
17
También puede valer la pena señalar que el autoboxing es en realidad solo azúcar sintáctico para llamar al valueOfmétodo de la clase box (como Integer.valueOf(int)). Es interesante que la JLS defina el desempaquetado exacto del azúcar, el uso de intValue()et al, pero no el desempaquetado del box.
gustafc
@gustafc no hay otra forma de desempaquetar una Integerque a través de la publicAPI oficial , es decir, llamando intValue(). Pero hay otras formas posibles de obtener una Integerinstancia para un intvalor, por ejemplo, un compilador puede generar código manteniendo y reutilizando Integerinstancias creadas previamente .
Holger
31
public class Scratch
{
   public static void main(String[] args)
    {
        Integer a = 1000, b = 1000;  //1
        System.out.println(a == b);

        Integer c = 100, d = 100;  //2
        System.out.println(c == d);
   }
}

Salida:

false
true

Sí, la primera salida se produce para comparar la referencia; 'a' y 'b': son dos referencias diferentes. En el punto 1, en realidad se crean dos referencias que son similares a:

Integer a = new Integer(1000);
Integer b = new Integer(1000);

La segunda salida se produce porque JVMintenta ahorrar memoria, cuando Integercae en un rango (de -128 a 127). En el punto 2 no se crea una nueva referencia de tipo Integer para 'd'. En lugar de crear un nuevo objeto para la variable de referencia de tipo Integer 'd', solo se asignó con el objeto creado anteriormente al que hace referencia 'c'. Todos estos son realizados por JVM.

Estas reglas de ahorro de memoria no son solo para Integer. para el propósito de ahorrar memoria, dos instancias de los siguientes objetos de envoltura (mientras se crean a través del boxing), siempre serán == donde sus valores primitivos son los mismos:

  • Booleano
  • Byte
  • Carácter de \ u0000 a \u007f(7f es 127 en decimal)
  • Corto y entero de -128 a 127
Razib
fuente
2
Longtambién tiene caché con el mismo rango que Integer.
Eric Wang
8

Los objetos enteros en algún rango (creo que tal vez -128 a 127) se almacenan en caché y se reutilizan. Los enteros fuera de ese rango obtienen un nuevo objeto cada vez.

Adam Crume
fuente
1
Este rango se puede ampliar utilizando java.lang.Integer.IntegerCache.highproperty. Es interesante que Long no tenga esa opción.
Aleksandr Kravets
5

Sí, hay una extraña regla de autoencuadre que se activa cuando los valores están en un cierto rango. Cuando asigna una constante a una variable de objeto, nada en la definición del lenguaje indica que se debe crear un nuevo objeto . Puede reutilizar un objeto existente de la caché.

De hecho, la JVM generalmente almacenará una caché de enteros pequeños para este propósito, así como valores como Boolean.TRUE y Boolean.FALSE.

Avi
fuente
4

Supongo que Java mantiene una caché de pequeños enteros que ya están "en caja" porque son muy comunes y ahorra mucho tiempo reutilizar un objeto existente que crear uno nuevo.

De todo género
fuente
4

Ese es un punto interesante. En el libro Effective Java, siempre se sugiere anular los iguales para sus propias clases. Además, para verificar la igualdad de dos instancias de objeto de una clase java, siempre use el método equals.

public class Scratch
{
    public static void main(String[] args)
    {
        Integer a = 1000, b = 1000;
        System.out.println(a.equals(b));

        Integer c = 100, d = 100;
        System.out.println(c.equals(d));
    }
}

devoluciones:

true
true
AmirHd
fuente
@Joel pidió otro tema, no la igualdad de enteros sino el comportamiento de los objetos en tiempo de ejecución.
Iliya Kuznetsov
3

En Java, el boxeo funciona en el rango entre -128 y 127 para un entero. Cuando usa números en este rango, puede compararlo con el operador ==. Para los objetos Integer fuera del rango, debe usar equals.

marvin
fuente
3

La asignación directa de un literal int a una referencia Integer es un ejemplo de auto-boxing, donde el compilador maneja el valor literal del código de conversión de objeto.

Entonces, durante la fase de compilación, el compilador se convierte Integer a = 1000, b = 1000;en Integer a = Integer.valueOf(1000), b = Integer.valueOf(1000);.

Entonces, es el Integer.valueOf()método el que realmente nos da los objetos enteros, y si miramos el código fuente del Integer.valueOf()método, podemos ver claramente que el método almacena en caché los objetos enteros en el rango de -128 a 127 (inclusive).

/**
 *
 * This method will always cache values in the range -128 to 127,
 * inclusive, and may cache other values outside of this range.
 *
 * @param  i an {@code int} value.
 * @return an {@code Integer} instance representing {@code i}.
 * @since  1.5
 */
 public static Integer valueOf(int i) {
     if (i >= IntegerCache.low && i <= IntegerCache.high)
         return IntegerCache.cache[i + (-IntegerCache.low)];
     return new Integer(i);
 }

Entonces, en lugar de crear y devolver nuevos objetos enteros, Integer.valueOf()el método devuelve objetos Integer del interno IntegerCachesi el literal int pasado es mayor que -128 y menor que 127.

Java almacena en caché estos objetos enteros porque este rango de enteros se usa mucho en la programación diaria, lo que indirectamente ahorra algo de memoria.

La caché se inicializa en el primer uso cuando la clase se carga en la memoria debido al bloque estático. El rango máximo de la caché puede ser controlado por el-XX:AutoBoxCacheMax opción JVM.

Este comportamiento de almacenamiento en caché no es aplicable solo para objetos Integer, similar a Integer. ByteCache, ShortCache, LongCache, CharacterCache para Byte, Short, Long, Characterrespectivamente.

Puede leer más en mi artículo Java Integer Cache - Why Integer.valueOf (127) == Integer.valueOf (127) Is True .

Naresh Joshi
fuente
0

En Java 5, se introdujo una nueva función para guardar la memoria y mejorar el rendimiento para el manejo de objetos de tipo Integer. Los objetos enteros se almacenan en caché internamente y se reutilizan a través de los mismos objetos referenciados.

  1. Esto es aplicable para valores enteros en el rango de –127 a +127 (valor entero máximo).

  2. Este almacenamiento en caché de enteros solo funciona en autoboxing. Los objetos enteros no se almacenarán en caché cuando se generen con el constructor.

Para obtener más detalles, consulte el siguiente enlace:

Caché de enteros en detalle

Rahul Maurya
fuente
0

Si verificamos el código fuente del Integerobjeto, encontraremos la fuente del valueOfmétodo así:

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

lo que puede explicar por qué los Integerobjetos, que en el rango de -128 ( Integer.low) a 127 ( Integer.high), son los mismos objetos referenciados durante el autoboxing. Y podemos ver que hay una clase que IntegerCachese encarga de la Integermatriz de caché, que es una clase de Integerclase interna estática privada .

Hay otro ejemplo interesante que puede ayudarnos a comprender esta extraña situación:

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {

      Class cache = Integer.class.getDeclaredClasses()[0]; 
      Field myCache = cache.getDeclaredField("cache"); 
      myCache.setAccessible(true);

      Integer[] newCache = (Integer[]) myCache.get(cache); 
      newCache[132] = newCache[133]; 

      Integer a = 2;
      Integer b = a + a;
      System.out.printf("%d + %d = %d", a, a, b); //The output is: 2 + 2 = 5    

}
L Joey
fuente