Operador ternário complicado em Java – autoboxing

Vamos ver o código Java simples no seguinte trecho:

public class Main { private int temp() { return true ? null : 0; // No compiler error - the compiler allows a return value of null // in a method signature that returns an int. } private int same() { if (true) { return null; // The same is not possible with if, // and causes a compile-time error - incompatible types. } else { return 0; } } public static void main(String[] args) { Main m = new Main(); System.out.println(m.temp()); System.out.println(m.same()); } } 

Nesse código Java mais simples, o método temp() não emite nenhum erro do compilador, mesmo que o tipo de retorno da function seja int e estamos tentando retornar o valor null (por meio da instrução return true ? null : 0; ). Quando compilado, isso obviamente causa a exceção de tempo de execução NullPointerException .

No entanto, parece que a mesma coisa está errada se representarmos o operador ternário com uma instrução if (como no same() método same() ), que emite um erro em tempo de compilation! Por quê?

O compilador interpreta null como uma referência nula para um Integer , aplica as regras de checkbox automática / unboxing para o operador condicional (como descrito em Java Language Specification, 15.25 ) e move-se alegremente. Isso gerará um NullPointerException em tempo de execução, que você pode confirmar tentando.

Eu acho que o compilador Java interpreta true ? null : 0 true ? null : 0 como uma expressão Integer , que pode ser implicitamente convertida em int , possivelmente fornecendo NullPointerException .

Para o segundo caso, a expressão null é do tipo nulo especial, portanto, o código return null faz a incompatibilidade de tipos.

Na verdade, tudo é explicado na especificação da linguagem Java .

O tipo de uma expressão condicional é determinado da seguinte forma:

  • Se o segundo e terceiro operandos tiverem o mesmo tipo (que pode ser o tipo nulo), então esse é o tipo da expressão condicional.

Portanto, o “nulo” no seu (true ? null : 0) obtém um tipo int e, em seguida, é autoboxed para Integer.

Tente algo como isto para verificar isso (true ? null : null) e você receberá o erro do compilador.

No caso da instrução if , a referência null não é tratada como uma referência Integer porque não está participando de uma expressão que força a interpretação como tal. Portanto, o erro pode ser facilmente detectado em tempo de compilation, porque é mais claramente um erro de tipo .

Quanto ao operador condicional, o Operador Condicional da Linguagem Java ? : ? : ”Responde bem nas regras de como a conversão de tipo é aplicada:

  • Se o segundo e terceiro operandos tiverem o mesmo tipo (que pode ser o tipo nulo), então esse é o tipo da expressão condicional.

    Não se aplica porque null não é int .


  • Se um dos segundo e terceiro operandos for do tipo booleano e o tipo do outro for do tipo booleano, então o tipo da expressão condicional é booleano.

    Não se aplica porque nem null nem int são boolean ou Boolean .


  • Se um dos segundo e terceiro operandos for do tipo nulo e o tipo do outro for um tipo de referência, então o tipo da expressão condicional é aquele tipo de referência.

    Não se aplica porque null é do tipo nulo, mas int não é um tipo de referência.


  • Caso contrário, se o segundo e o terceiro operandos possuírem tipos conversíveis (§5.1.8) para tipos numéricos, então existem vários casos: […]

    Aplica-se: null é tratado como conversível para um tipo numérico e é definido em §5.1.8 “Unboxing Conversion” para lançar um NullPointerException .

A primeira coisa a ter em mente é que os operadores ternários Java possuem um “tipo”, e isso é o que o compilador determinará e considerará, não importando quais sejam os tipos real / real do segundo ou terceiro parâmetro. Dependendo de vários fatores, o tipo de operador ternário é determinado de maneiras diferentes, conforme ilustrado na Especificação de Linguagem Java 15.26.

Na questão acima, devemos considerar o último caso:

Caso contrário, o segundo e terceiro operandos são dos tipos S1 e S2, respectivamente. Seja T1 o tipo que resulta da aplicação da conversão de boxe em S1 e seja T2 o tipo resultante da aplicação da conversão de boxe em S2 . O tipo da expressão condicional é o resultado da aplicação da conversão de captura (§5.1.10) para lub (T1, T2) (§15.12.2.7).

Este é, de longe, o caso mais complexo uma vez que você dê uma olhada na aplicação de conversão de captura (§5.1.10) e, acima de tudo, em lub (T1, T2) .

Em linguagem simples e depois de uma simplificação extrema, podemos descrever o processo como calculando a “Superclass Menos Comum” (sim, pense no MMC) dos segundo e terceiro parâmetros. Isso nos dará o operador ternário “type”. Novamente, o que acabei de dizer é uma simplificação extrema (considere classs que implementam várias interfaces comuns).

Por exemplo, se você tentar o seguinte:

 long millis = System.currentTimeMillis(); return(true ? new java.sql.Timestamp(millis) : new java.sql.Time(millis)); 

Você notará que o tipo resultante da expressão condicional é java.util.Date já que é a “Superclass Menos Comum” para o par Timestamp / Time .

Como null pode ser autoboxado para qualquer coisa, a “Superclass Menos Comum” é a class Integer e este será o tipo de retorno da expressão condicional (operador ternário) acima. O valor de retorno será então um ponteiro nulo do tipo Integer e é isso que será retornado pelo operador ternário.

Em tempo de execução, quando o Java Virtual Machine libera o Integer um NullPointerException é lançado. Isso acontece porque a JVM tenta chamar a function null.intValue() , em que null é o resultado da autoboxing.

Na minha opinião (e uma vez que minha opinião não está na especificação da linguagem Java, muitas pessoas acharão errado de qualquer forma), o compilador faz um trabalho ruim ao avaliar a expressão em sua pergunta. Dado que você escreveu true ? param1 : param2 true ? param1 : param2 o compilador deve determinar imediatamente que o primeiro parâmetro – null – será retornado e deve gerar um erro do compilador. Isso é um pouco semelhante a quando você escreve while(true){} etc... e o compilador reclama do código abaixo do loop e o sinaliza com Unreachable Statements .

Seu segundo caso é bastante simples e esta resposta já é muito longa …;)

CORREÇÃO:

Depois de outra análise, acredito que estava errado dizer que um valor null pode ser encaixotado / autoboxado para qualquer coisa. Falando sobre a class Integer, o boxing explícito consiste em invocar o new Integer(...) construtor new Integer(...) ou talvez o Integer.valueOf(int i); (Eu encontrei esta versão em algum lugar). O primeiro lançaria um NumberFormatException (e isso não acontece), enquanto o segundo não faria sentido, já que um int não pode ser null

Na verdade, no primeiro caso a expressão pode ser avaliada, já que o compilador sabe que deve ser avaliada como um Integer , porém no segundo caso o tipo do valor de retorno ( null ) não pode ser determinado, então não pode ser compilado. Se você converter para Integer , o código será compilado.

 private int temp() { if (true) { Integer x = null; return x;// since that is fine because of auto-boxing then the returned value could be null //in other words I can say x could be null or new Integer(intValue) or a intValue } return (true ? null : 0); //this will be prefectly legal null would be refrence to Integer. The concept is one the returned //value can be Integer // then null is accepted to be a variable (-refrence variable-) of Integer } 

Que tal agora:

 public class ConditionalExpressionType { public static void main(String[] args) { String s = ""; s += (true ? 1 : "") instanceof Integer; System.out.println(s); String t = ""; t += (!true ? 1 : "") instanceof String; System.out.println(t); } } 

A saída é verdadeira, verdadeira.

A cor do Eclipse codifica o 1 na expressão condicional como autoboxed.

Meu palpite é que o compilador está vendo o tipo de retorno da expressão como Object.