Diferenças no desempacotamento automático entre Java 6 e Java 7

107

Observei uma diferença no comportamento de desempacotamento automático entre o Java SE 6 e o ​​Java SE 7. Estou me perguntando por que isso acontece, porque não consigo encontrar nenhuma documentação de alterações nesse comportamento entre essas duas versões.

Aqui está um exemplo simples:

Object[] objs = new Object[2];
objs[0] = new Integer(5);
int myInt = (int)objs[0];

Isso compila bem com javac do Java SE 7. No entanto, se eu der ao compilador o argumento "-source 1.6", recebo um erro na última linha:

inconvertible types
found   : java.lang.Object
required: int

Tentei baixar o Java SE 6 para compilar com o compilador nativo da versão 6 (sem qualquer opção -source). Ele concorda e dá o mesmo erro acima.

Então, o que dá? A partir de um pouco mais de experimentação, parece que o unboxing em Java 6 só pode desempacotar valores que claramente (em tempo de compilação) sejam do tipo inbox. Por exemplo, isso funciona em ambas as versões:

Integer[] objs = new Integer[2];
objs[0] = new Integer(5);
int myInt = (int)objs[0];

Portanto, parece que entre o Java 6 e 7, o recurso unboxing foi aprimorado para que pudesse lançar e desempacotar tipos de objeto de uma só vez, sem saber (em tempo de compilação) que o valor é do tipo in a box apropriado. No entanto, lendo a especificação da linguagem Java ou postagens de blog que foram escritas na época em que o Java 7 foi lançado, não consigo ver nenhuma mudança nisso, então estou me perguntando o que é a mudança e como esse "recurso" é chamado ?

Só uma curiosidade: Devido à mudança, é possível acionar unboxings "errados":

Object[] objs = new Float[2];
objs[0] = new Float(5);
int myInt = (int)objs[0];

Isso compila bem, mas fornece uma ClassCastException em tempo de execução.

Alguma referência sobre isso?

Morty
fonte
17
Interessante. Um novo ingrediente para a bagunça do autoboxing. Acho que seu exemplo poderia ser mais simples e claro com um único objeto em vez de um array. Integer obj = new Integer(2); int x = (int)obj;: funciona em Java 7, dá erro em Java 6.
leonbloy
1
Qual JDK você está usando? Também pode ter a ver com fornecedores diferentes ...
barfuin
1
@leonbloy: Bom ponto sobre a simplificação, eu simplifiquei um pouco (do meu código original), mas de alguma forma parei muito cedo!
Morty de
@Thomas: Foi o JDK mais recente (para cada versão) da Oracle que usei.
Morty de
2
Outra razão para nunca usar autoboxing.
gyorgyabraham

Respostas:

92

Parece que a linguagem na seção 5.5 Casting Conversion do Java 7 JLS foi atualizada em comparação com a mesma seção no Java 5/6 JLS , provavelmente para esclarecer as conversões permitidas.

Java 7 JLS diz

Uma expressão de um tipo de referência pode sofrer conversão de casting para um tipo primitivo sem erro, por conversão de unboxing.

Java 5/6:

Um valor de um tipo de referência pode ser convertido em um tipo primitivo por conversão unboxing (§5.1.8).

O Java 7 JLS também contém uma tabela (tabela 5.1) de conversões permitidas (esta tabela não está incluída no Java 5/6 JLS) de tipos de referência para primitivos. Isso lista explicitamente os casts de Object para primitivos como uma conversão de referência de estreitamento com unboxing.

O motivo é explicado neste e - mail :

Resumindo: se a especificação. permite (Object) (int) também deve estar permitindo (int) (Object).

Mark Rotteveel
fonte
35

Você está certo; para ser mais simples:

Object o = new Integer(1234);
int x = (int) o;

Isso funciona no Java 7, mas dá um erro de compilação no Java 6 e anteriores. Estranhamente, esse recurso não é documentado de maneira destacada; por exemplo, não é mencionado aqui . É discutível se é um novo recurso ou uma correção de bug (ou um novo bug?), Veja algumas informações relacionadas e discussão . O consenso parece apontar para uma ambigüidade na especificação original, o que levou a uma implementação ligeiramente incorreta / inconsistente em Java 5/6, que foi corrigida em 7, porque era crítica para a implementação de JSR 292 (Dynamically Typed Languages).

O autoboxing do Java agora tem mais algumas armadilhas e surpresas. Por exemplo

Object obj = new Integer(1234);
long x = (long)obj;

irá compilar, mas falhar (com ClassCastException) em tempo de execução. Isso, em vez disso, funcionará:

long x = (long)(int)obj;

leonbloy
fonte
2
Obrigado pela resposta. No entanto, há uma coisa que não entendo. Este é um esclarecimento do JLS e das implementações que o acompanham (cf. a discussão por email), mas por que isso seria feito para acomodar outras linguagens digitadas no JVM? Afinal, é uma mudança na linguagem, não na VM: o comportamento de conversão da VM funciona como sempre, o compilador implementa esse recurso usando o mecanismo existente para conversão em Integer e chamando .intValue (). Então, como essa mudança na linguagem Java adequada pode ajudar a executar outras linguagens na VM? Concordo que seu link sugere isso, apenas imaginando.
Morty,