¿Por qué 128 == 128 es falso pero 127 == 127 es verdadero cuando se comparan contenedores de enteros en Java?

173
class D {
    public static void main(String args[]) {
        Integer b2=128;
        Integer b3=128;
        System.out.println(b2==b3);
    }
}

Salida:

false

class D {
    public static void main(String args[]) {
        Integer b2=127;
        Integer b3=127;
        System.out.println(b2==b3);
    }
}

Salida:

true

Nota: los números entre -128 y 127 son verdaderos.

vipin k.
fuente
10
Puede encontrar bexhuff.com/2006/11/java-1-5-autoboxing-wackyness informativo.
Dominic Rodger
1
¿Cómo llegaste al punto de hacer esta pregunta? es realmente divertido, pero uno nunca se encuentra con algo así "en el mundo real" ... o?
Mare Infinitus

Respuestas:

218

Cuando compila un número literal en Java y lo asigna a un entero (capital I), el compilador emite:

Integer b2 =Integer.valueOf(127)

Esta línea de código también se genera cuando usa autoboxing.

valueOf se implementa de tal manera que ciertos números se "agrupan" y devuelve la misma instancia para valores menores que 128.

Desde el código fuente de java 1.6, línea 621:

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

El valor de highse puede configurar en otro valor, con la propiedad del sistema.

-Djava.lang.Integer.IntegerCache.high = 999

Si ejecuta su programa con esa propiedad del sistema, ¡saldrá verdadero!

La conclusión obvia: nunca confíes en que dos referencias sean idénticas, siempre compáralas con el .equals()método.

Entonces b2.equals(b3)imprimirá verdadero para todos los valores lógicamente iguales de b2, b3.

Tenga en cuenta que el Integercaché no está allí por razones de rendimiento, sino más bien para cumplir con el JLS, sección 5.1.7 ; Se debe dar identidad de objeto para los valores -128 a 127 inclusive.

Integer # valueOf (int) también documenta este comportamiento:

Es probable que este método produzca un rendimiento de espacio y tiempo significativamente mejor al almacenar en caché los valores solicitados con frecuencia. Este método siempre almacenará en caché los valores en el rango de -128 a 127, inclusive, y puede almacenar en caché otros valores fuera de este rango.

Andreas Petersson
fuente
1
tenga en cuenta que Java ignorará los valores inferiores a 127 y los valores superiores a Integer.MAX_VALUE-128 estarán limitados.
Andreas Petersson el
Los enteros se almacenan en caché para los valores de bytes en Java 5 y superiores, lo que hace que el nuevo entero (1) == nuevo entero (1). Sin embargo, este no es el caso en Java 1.4 o inferior, así que tenga cuidado si finalmente tiene que degradar a ese entorno.
MetroidFan2002
11
No, esto está mal. new Integer (1) == new Integer (1) es falso independientemente de la jvm. AFAIK ningún compilador engañará con la palabra clave "nueva". DEBE siempre instanciar un nuevo objeto.
Andreas Petersson el
1
@Holger punto interesante. Pero es técnicamente posible reemplazar la clase Integer del JDK con una implicación personalizada ... (no pregunte por qué alguien estaría tan loco), entonces podría tener efectos secundarios que no se pueden optimizar
Andreas Petersson
1
@AndreasPetersson seguro. "Compilador" significa el compilador JIT, que conoce con precisión la clase de implementación real y solo puede optimizar, si el constructor no tiene efectos secundarios. O bien, optimice la expresión para reproducir solo los efectos secundarios, seguido del uso false. En realidad, esto puede suceder hoy, como un efecto secundario de la aplicación de Análisis de escape y Reemplazo escalar.
Holger
24

Almacenamiento automático en caché -128 a 127. Esto se especifica en JLS ( 5.1.7 ).

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

Una regla simple para recordar cuando se trata de objetos es: úselo .equalssi desea verificar si los dos objetos son "iguales", úselo ==cuando quiera ver si apuntan a la misma instancia.

Michael Lloyd Lee mlk
fuente
1
Nota: el JLS cambió en Java 9. Esto ahora solo está garantizado para expresiones constantes de tiempo de compilación ; ver actualización de la respuesta aceptada.
Stephen C
9

El uso de tipos de datos primitivos, ints, produciría verdadero en ambos casos, la salida esperada.

Sin embargo, dado que está utilizando objetos Integer, el operador == tiene un significado diferente.

En el contexto de los objetos, == verifica si las variables se refieren a la misma referencia de objeto.

Para comparar el valor de los objetos, debe usar el método equals () Ej.

 b2.equals(b1)

que indicará si b2 es menor que b1, mayor o igual que (consulte la API para obtener más detalles)

chrisbunney
fuente
7

Es optimización de memoria en Java relacionado.

Para ahorrar en memoria, Java 'reutiliza' todos los objetos de contenedor cuyos valores se encuentran en los siguientes rangos:

Todos los valores booleanos (verdadero y falso)

Todos los valores de bytes

Todos los valores de caracteres de \ u0000 a \ u007f (es decir, de 0 a 127 en decimal)

Todos los valores cortos e enteros de -128 a 127.

Desarrollador Marius Žilėnas
fuente
3

Eche un vistazo a Integer.java, si el valor está entre -128 y 127, usará el grupo en caché, por lo (Integer) 1 == (Integer) 1tanto , mientras(Integer) 222 != (Integer) 222

 /**
 * Returns an {@code Integer} instance representing the specified
 * {@code int} value.  If a new {@code Integer} instance is not
 * required, this method should generally be used in preference to
 * the constructor {@link #Integer(int)}, as this method is likely
 * to yield significantly better space and time performance by
 * caching frequently requested values.
 *
 * 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) {
    assert IntegerCache.high >= 127;
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}       
Yanghaogn
fuente
0

Otras respuestas describen por qué se pueden observar los efectos observados, pero eso no tiene nada que ver con los programadores (interesante, sin duda, pero algo que debes olvidar al escribir el código real).

Para comparar objetos enteros para igualdad, use el equals método

No intente comparar objetos enteros para igualdad utilizando el operador de identidad, == .

Puede suceder que algunos valores iguales sean objetos idénticos, pero esto no es algo en lo que generalmente se deba confiar.

usuario13463803
fuente
-4

Escribí lo siguiente ya que este problema no es solo específico de Integer. Mi conclusión es que, la mayoría de las veces, si usa la API incorrectamente, verá un comportamiento incorrecto. Úselo correctamente y debería ver el comportamiento correcto:

public static void main (String[] args) {
    Byte b1=127;
    Byte b2=127;

    Short s1=127; //incorrect should use Byte
    Short s2=127; //incorrect should use Byte
    Short s3=128;
    Short s4=128;

    Integer i1=127; //incorrect should use Byte
    Integer i2=127; //incorrect should use Byte
    Integer i3=128;
    Integer i4=128;

    Integer i5=32767; //incorrect should use Short
    Integer i6=32767; //incorrect should use Short

    Long l1=127L;           //incorrect should use Byte
    Long l2=127L;           //incorrect should use Byte
    Long l3=13267L;         //incorrect should use Short
    Long l4=32767L;         //incorrect should use Short
    Long l5=2147483647L;    //incorrect should use Integer 
    Long l6=2147483647L;    //incorrect should use Integer
    Long l7=2147483648L;
    Long l8=2147483648L;

    System.out.print(b1==b2); //true  (incorrect) Used API correctly
    System.out.print(s1==s2); //true  (incorrect) Used API incorrectly
    System.out.print(i1==i2); //true  (incorrect) Used API incorrectly
    System.out.print(l1==l2); //true  (incorrect) Used API incorrectly

    System.out.print(s3==s4); //false (correct) Used API correctly
    System.out.print(i3==i4); //false (correct) Used API correctly
    System.out.print(i5==i6); //false (correct) Used API correctly
    System.out.print(l3==l4); //false (correct) Used API correctly
    System.out.print(l7==l8); //false (correct) Used API correctly
    System.out.print(l5==l6); //false (correct) Used API incorrectly

}
thejartender
fuente