Boxe estranho inteiro em Java

114

Acabei de ver um código semelhante 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);
    }
}

Quando executado, este bloco de código imprimirá:

false
true

Eu entendo porque o primeiro é false: porque os dois objetos são objetos separados, então o ==compara as referências. Mas não consigo descobrir, por que a segunda declaração está retornando true? Existe alguma regra de autoboxing estranha que entra em ação quando o valor de um inteiro está em um determinado intervalo? O que está acontecendo aqui?

Joel
fonte
1
Parece um
3
@RC - Não é bem ingênuo, mas uma situação semelhante é discutida. Obrigado pela referência embora.
Joel
2
isto é horrível. é por isso que nunca entendi o objetivo de todo esse primitivo, mas objeto, mas ambos, mas auto-encaixotado, mas depende, mas aaaaaaaaargh.
njzk2
1
@Razib: A palavra "autoboxing" não é um código, então não a formate assim.
Tom

Respostas:

102

A truelinha é realmente garantida pela especificação do idioma. Da seção 5.1.7 :

Se o valor p sendo encaixotado for verdadeiro, falso, um byte, um caractere no intervalo \ u0000 a \ u007f, ou um int ou número curto entre -128 e 127, então sejam r1 e r2 os resultados de quaisquer duas conversões de encaixotamento de p. É sempre o caso que r1 == r2.

A discussão continua, sugerindo que, embora sua segunda linha de saída seja garantida, a primeira não é (veja o último parágrafo citado abaixo):

Idealmente, encaixotar um dado valor primitivo p sempre produziria uma referência idêntica. Na prática, isso pode não ser viável usando as técnicas de implementação existentes. As regras acima são um compromisso pragmático. A cláusula final acima requer que certos valores comuns sempre sejam encaixotados em objetos indistinguíveis. A implementação pode armazená-los em cache, preguiçosamente ou ansiosamente.

Para outros valores, esta formulação não permite quaisquer suposições sobre a identidade dos valores encaixotados por parte do programador. Isso permitiria (mas não exigiria) o compartilhamento de algumas ou de todas essas referências.

Isso garante que, na maioria dos casos comuns, o comportamento será o desejado, sem impor uma penalidade de desempenho indevida, especialmente em dispositivos pequenos. Implementações com menos memória limitada podem, por exemplo, armazenar em cache todos os caracteres e curtos, bem como inteiros e longos na faixa de -32K - + 32K.

Jon Skeet
fonte
17
Também pode ser importante notar que o autoboxing é, na verdade, apenas um açúcar sintático para chamar o valueOfmétodo da classe box (como Integer.valueOf(int)). Interessante que o JLS define o desugaring de unboxing exato - usando intValue()et al - mas não o desugaring de boxing.
gustafc
@gustafc não há outra maneira de desempacotar um Integersenão através da publicAPI oficial , ou seja, chamando intValue(). Mas existem outras maneiras possíveis de obter uma Integerinstância para um intvalor, por exemplo, um compilador pode gerar manutenção de código e reutilizar Integerinstâncias criadas anteriormente .
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);
   }
}

Resultado:

false
true

Sim, a primeira saída é produzida para comparação de referência; 'a' e 'b' - são duas referências diferentes. No ponto 1, na verdade, duas referências são criadas que é semelhante a -

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

A segunda saída é produzida porque JVMtenta economizar memória, quando Integercai em uma faixa (de -128 a 127). No ponto 2, nenhuma nova referência do tipo Inteiro é criada para 'd'. Em vez de criar um novo objeto para a variável de referência do tipo Integer 'd', ele apenas é atribuído ao objeto criado anteriormente referenciado por 'c'. Tudo isso é feito por JVM.

Essas regras de economia de memória não são apenas para números inteiros. para fins de economia de memória, duas instâncias dos seguintes objetos de invólucro (enquanto criados por meio de boxing), serão sempre == onde seus valores primitivos são os mesmos -

  • boleano
  • Byte
  • Caractere de \ u0000 a \u007f(7f é 127 em decimal)
  • Curto e inteiro de -128 a 127
Razib
fonte
2
Longtambém tem cache com o mesmo alcance Integer.
Eric Wang
8

Objetos inteiros em algum intervalo (acho que talvez -128 a 127) são armazenados em cache e reutilizados. Inteiros fora desse intervalo obtêm um novo objeto a cada vez.

Adam Crume
fonte
1
Este intervalo pode ser estendido usando java.lang.Integer.IntegerCache.highproperty. Interessante que Long não tem essa opção.
Aleksandr Kravets
5

Sim, existe uma estranha regra de autoboxing que entra em ação quando os valores estão em um determinado intervalo. Quando você atribui uma constante a uma variável Object, nada na definição da linguagem diz que um novo objeto deve ser criado. Ele pode reutilizar um objeto existente do cache.

Na verdade, a JVM geralmente armazena um cache de pequenos inteiros para esse propósito, bem como valores como Boolean.TRUE e Boolean.FALSE.

Avi
fonte
4

Meu palpite é que Java mantém um cache de pequenos inteiros que já estão 'encaixotados' porque são muito comuns e economiza muito tempo reutilizar um objeto existente do que criar um novo.

Omniforme
fonte
4

Esse é um ponto interessante. No livro Effective Java sugere sempre substituir equals para suas próprias classes. Além disso, para verificar a igualdade de duas instâncias de objeto de uma classe java, sempre use o 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));
    }
}

retorna:

true
true
AmirHd
fonte
@Joel pediu muito outro tópico, não igualdade de inteiros, mas comportamento de tempo de execução de objetos.
Iliya Kuznetsov
3

Em Java, o boxe funciona no intervalo entre -128 e 127 para um inteiro. Quando estiver usando números neste intervalo, você pode compará-los com o operador ==. Para objetos inteiros fora do intervalo, você deve usar iguais.

marvin
fonte
3

A atribuição direta de um literal int para uma referência Integer é um exemplo de boxing automático, em que o valor literal para o código de conversão de objeto é tratado pelo compilador.

Portanto, durante a fase de compilação, o compilador converte Integer a = 1000, b = 1000;para Integer a = Integer.valueOf(1000), b = Integer.valueOf(1000);.

Portanto, é o Integer.valueOf()método que realmente nos dá os objetos inteiros, e se olharmos o código-fonte do Integer.valueOf()método, podemos ver claramente que o método armazena em cache os objetos inteiros na faixa 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);
 }

Portanto, em vez de criar e retornar novos objetos inteiros, Integer.valueOf()o método retorna objetos inteiros do interno IntegerCachese o literal int passado for maior que -128 e menor que 127.

Java armazena em cache esses objetos inteiros porque esse intervalo de inteiros é muito usado na programação do dia a dia, o que indiretamente economiza alguma memória.

O cache é inicializado no primeiro uso quando a classe é carregada na memória por causa do bloco estático. O intervalo máximo do cache pode ser controlado pela -XX:AutoBoxCacheMaxopção JVM.

Este comportamento de armazenamento em cache não é aplicável apenas a objetos Integer, semelhante a Integer.IntegerCache que também temos ByteCache, ShortCache, LongCache, CharacterCachepara Byte, Short, Long, Characterrespectivamente.

Você pode ler mais em meu artigo Java Integer Cache - Why Integer.valueOf (127) == Integer.valueOf (127) Is True .

Naresh Joshi
fonte
0

No Java 5, um novo recurso foi introduzido para economizar memória e melhorar o desempenho para manipulações de objetos do tipo Inteiro. Objetos inteiros são armazenados em cache internamente e reutilizados por meio dos mesmos objetos referenciados.

  1. Isso é aplicável para valores inteiros no intervalo entre –127 a +127 (valor inteiro máximo).

  2. Este cache inteiro funciona apenas em autoboxing. Objetos inteiros não serão armazenados em cache quando forem construídos usando o construtor.

Para obter mais detalhes, acesse o link abaixo:

Cache Inteiro em Detalhe

Rahul Maurya
fonte
0

Se verificarmos o código-fonte do Integerobjeto, encontraremos a fonte do valueOfmétodo assim:

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

o que pode explicar por que os Integerobjetos, que estão no intervalo de -128 ( Integer.low) a 127 ( Integer.high), são os mesmos objetos referenciados durante o autoboxing. E podemos ver que há uma classe que IntegerCachecuida do Integerarray de cache, que é uma classe interna estática privada de Integerclasse.

Há outro exemplo interessante que pode nos ajudar a entender essa situação estranha:

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
fonte