Por que o compilador/JVM não pode simplesmente fazer o autoboxing “simplesmente funcionar”?

StackOverflow https://stackoverflow.com/questions/2602636

Pergunta

Autoboxing é bastante assustador.Embora eu compreenda perfeitamente a diferença entre == e .equals Não posso deixar de evitar que o seguinte bug me acabe:

    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));

Isso imprime

true
false

Por que eles fizeram isso dessa maneira?Tem algo a ver com números inteiros armazenados em cache, mas se for esse o caso, por que eles simplesmente não armazenam em cache todos os números inteiros usados ​​pelo programa?Ou por que a JVM nem sempre descompacta automaticamente para o primitivo?

Imprimir false false ou true true teria sido muito melhor.

EDITAR

Discordo sobre a quebra do código antigo.Tendo foo.get(0) == bar.get(0) retorne verdadeiro, você já quebrou o código.

Isso não pode ser resolvido no nível do compilador, substituindo Integer por int no código de bytes (desde que nunca seja atribuído nulo)

Foi útil?

Solução

  • Por que eles fizeram isso dessa maneira?

Cada número inteiro entre -128 e 127 é armazenado em cache por java.Eles fizeram isso, supostamente, para benefício do desempenho.Mesmo que quisessem voltar atrás nesta decisão agora, é improvável que o fizessem.Se alguém construísse código dependendo disso, seu código quebraria quando fosse retirado.Para codificação de hobby, isso talvez não importe, mas para código corporativo, as pessoas ficam chateadas e processos judiciais acontecem.

  • Por que eles simplesmente não armazenam em cache todos os números inteiros usados ​​pelo programa?

Todos os números inteiros não podem ser armazenados em cache, porque as implicações de memória seriam enormes.

  • Por que a JVM nem sempre descompacta automaticamente para o primitivo?

Porque a JVM não pode saber o que você queria.Além disso, essa alteração pode facilmente quebrar o código legado não criado para lidar com esse caso.

Se a JVM for automaticamente desempacotada para primitivos em chamadas para ==, esse problema se tornará MAIS confuso.Agora você precisa lembrar que == sempre compara referências de objetos, a menos que os objetos possam ser desembalados.Isso causaria casos ainda mais estranhos e confusos, como o que você declarou acima.

Em vez de se preocupar muito com isso, lembre-se desta regra:

NUNCA compare objetos com == a menos que você pretenda compará-los por suas referências.Se você fizer isso, não consigo pensar em um cenário em que você se depararia com um problema.

Outras dicas

Você pode imaginar quão ruim seria o desempenho se cada Integer transportado em sobrecarga para internamento?Também não funciona para new Integer.

A linguagem Java (não um problema de JVM) nem sempre pode ser descompactada automaticamente porque o código projetado para Java anterior a 1.5 ainda deve funcionar.

Integers no intervalo de bytes são o mesmo objeto, porque estão armazenados em cache. Integers fora do intervalo de bytes não estão.Se todos os números inteiros fossem armazenados em cache, imagine a memória necessária.

E de aqui

O resultado de toda essa mágica é que você pode ignorar em grande parte a distinção entre int e Integer, com algumas ressalvas.Uma expressão inteira pode ter um valor nulo.Se o seu programa tentar autounbox nulo, ele lançará uma NullPointerException.O operador == realiza comparações de identidade de referência em expressões inteiras e comparações de igualdade de valor em expressões int.Finalmente, existem custos de desempenho associados ao boxing e unboxing, mesmo que seja feito automaticamente

Se você pular completamente o autoboxing, ainda terá esse comportamento.

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

Seja mais explícito se quiser um comportamento 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 é uma razão pela qual o Eclipse tem autoboxing como um aviso por padrão.

Muitas pessoas têm problemas com esse problema, até mesmo pessoas que escrevem livros sobre Java.

Em Programação Java Profissional, poucos centímetros abaixo onde o autor fala sobre problemas com o uso de números inteiros em caixa automática como uma chave em um IdentityHashMap, ele usa chaves inteiras em caixa automática em um WeakHashMap.Os valores de exemplo que ele usa são maiores que 128, portanto, sua chamada de coleta de lixo foi bem-sucedida.Se alguém usasse seu exemplo e usasse valores menores que 128, seu exemplo falharia (devido à chave ser armazenada em cache permanente).

Quando você escreve

foo.get(0)

o compilador não importa como você criou a lista.Ele apenas analisa o tipo de tempo de compilação do List foo.Portanto, se for um List<Integer>, ele o tratará como um List<Integer>, como deveria fazer, e o get() de um List<Integer> sempre retorna um Integer.Se você quiser usar o == então você tem que escrever

System.out.println(foo.get(0).intValue() == bar.get(0).intValue());

não

System.out.println(foo.get(0) == bar.get(0));

porque isso tem um significado totalmente diferente.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top