Por que 128 == 128 é falso, mas 127 == 127 é verdadeiro ao comparar wrappers Inteiros em Java?

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

Resultado:

false

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

Resultado:

true

Nota: Números entre -128 e 127 são verdadeiros.

vipin k.
fonte
10
Você pode encontrar informações sobre bexhuff.com/2006/11/java-1-5-autoboxing-wackyness .
Dominic Rodger
1
como você chegou ao ponto de fazer essa pergunta? é muito divertido, mas nunca se depara com algo assim "no mundo real" ... ou?
Mare Infinitus

Respostas:

217

Quando você compila um literal numérico em Java e o atribui a um número inteiro (capital I), o compilador emite:

Integer b2 =Integer.valueOf(127)

Essa linha de código também é gerada quando você usa a caixa automática.

valueOf é implementado de forma que certos números sejam "agrupados" e retorna a mesma instância para valores menores que 128.

No código-fonte java 1.6, linha 621:

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

O valor de highpode ser configurado para outro valor, com a propriedade do sistema.

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

Se você executar o seu programa com essa propriedade do sistema, o resultado será verdadeiro!

A conclusão óbvia: nunca confie em duas referências idênticas, sempre as compare com o .equals()método.

Portanto, b2.equals(b3)será impresso true para todos os valores logicamente iguais de b2, b3.

Observe que o Integercache não existe por motivos de desempenho, mas para estar em conformidade com o JLS, seção 5.1.7 ; a identidade do objeto deve ser fornecida para os valores -128 a 127 inclusive.

O número inteiro # valueOf (int) também documenta esse comportamento:

é provável que esse método produza desempenho de espaço e tempo significativamente melhor armazenando em cache os valores solicitados com freqüência. Este método sempre armazenará em cache valores no intervalo -128 a 127, inclusive, e poderá armazenar em cache outros valores fora desse intervalo.

Andreas Petersson
fonte
1
observe que valores menores que 127 serão ignorados pelo java e valores maiores que Inteiro.MAX_VALUE-128 serão limitados.
Andreas Petersson
Os números inteiros são armazenados em cache para valores de bytes em Java 5 e superior, tornando o novo número inteiro (1) == novo número inteiro (1). No entanto, esse não é o caso no Java 1.4 ou inferior, portanto, tenha cuidado se você precisar fazer o downgrade para esse ambiente.
MetroidFan2002
11
não, isso está errado. novo inteiro (1) == novo inteiro (1) é falso, independentemente da jvm. O AFAIK nenhum compilador enganará a palavra-chave "nova". DEVE sempre instanciar um novo objeto.
Andreas Petersson
1
@Holger ponto interessante. Mas é tecnicamente possível substituir a classe Integer do JDK por um impl ... (não pergunte por que alguém seria tão insano) - isso poderia ter efeitos colaterais que não podem ser otimizados
Andreas Petersson
1
@AndreasPetersson sure. “Compilador” significa o compilador JIT, que conhece com precisão a classe de implementação real e só pode otimizar se o construtor não tiver efeitos colaterais. Ou otimize a expressão para reproduzir apenas os efeitos colaterais, seguidos do uso false. Na verdade, isso já pode acontecer hoje, como efeito colateral da aplicação da Análise de escape e da substituição escalar.
Holger
24

A autoboxing armazena em cache -128 a 127. Isso é especificado no JLS ( 5.1.7 ).

Se o valor p em caixa for verdadeiro, falso, um byte, um caractere no intervalo \ u0000 a \ u007f ou um número int ou curto entre -128 e 127, então r1 e r2 serão o resultado de duas conversões de boxe de p. É sempre o caso que r1 == r2.

Uma regra simples a ser lembrada ao lidar com objetos é - use .equalsse você deseja verificar se os dois objetos são "iguais", use ==quando quiser ver se eles apontam para a mesma instância.

Michael Lloyd Lee mlk
fonte
1
Nota: o JLS foi alterado no Java 9. Isso agora é garantido apenas para expressões constantes de tempo de compilação ; veja atualização para resposta aceita.
Stephen C
9

O uso de tipos de dados primitivos, ints, produziria true em ambos os casos, a saída esperada.

No entanto, como você está usando objetos Inteiros, o operador == tem um significado diferente.

No contexto de objetos, == verifica se as variáveis ​​se referem à mesma referência de objeto.

Para comparar o valor dos objetos, você deve usar o método equals ()

 b2.equals(b1)

que indicará se b2 é menor que b1, maior que ou igual a (verifique a API para obter detalhes)

chrisbunney
fonte
7

É otimização de memória relacionada a Java.

Para economizar memória, o Java 'reutiliza' todos os objetos do wrapper cujos valores caem nos seguintes intervalos:

Todos os valores booleanos (verdadeiro e falso)

Todos os valores de bytes

Todos os valores de caracteres de \ u0000 a \ u007f (ou seja, 0 a 127 em decimal)

Todos os valores curtos e inteiros de -128 a 127.

Desenvolvedor Marius Žilėnas
fonte
3

Dê uma olhada no Integer.java, se o valor estiver entre -128 e 127, ele usará o pool em cache, portanto, (Integer) 1 == (Integer) 1enquanto(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
fonte
0

Outras respostas descrevem por que os efeitos observados podem ser observados, mas isso não vem ao caso para programadores (interessante, certamente, mas algo que você deve esquecer ao escrever código real).

Para comparar objetos Inteiros para igualdade, use o equalsmétodo

Não tente comparar objetos Inteiros para igualdade usando o operador de identidade ==,.

Pode acontecer que alguns valores iguais sejam objetos idênticos, mas isso não é algo em que geralmente se deve confiar.

user13463803
fonte
-4

Eu escrevi o seguinte, pois esse problema não é apenas específico para Inteiro. Minha conclusão é que, na maioria das vezes, se você usar a API incorretamente, verá um comportamento incorreto. Use-o corretamente e você verá o comportamento correto:

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
fonte