Operador condicional de Java?: Tipo de resultado

Estou um pouco intrigado com o operador condicional. Considere as duas linhas seguintes:

Float f1 = false? 1.0f: null; Float f2 = false? 1.0f: false? 1.0f: null; 

Por que f1 se torna nulo e a segunda instrução lança um NullPointerException?

Langspec-3.0 para 15.25 sais:

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

Então, para false?1.0f:null T1 é Float e T2 é o tipo null. Mas qual é o resultado de lub(T1,T2) ? Este parágrafo 15.12.2.7 é um pouco demais …

BTW, estou usando o 1.6.0_18 no Windows.

PS: Eu sei que Float f2 = false? (Float) 1.0f: false? (Float) 1.0f: null; Float f2 = false? (Float) 1.0f: false? (Float) 1.0f: null; não joga NPE.

A diferença é a tipificação estática das expressões no tempo de compilation:

Resumo

 E1: `(false ? 1.0f : null)` - arg 2 '1.0f' : type float, - arg 3 'null' : type null - therefore operator ?: : type Float (see explanation below) - therefore autobox arg2 - therefore autobox arg3 E2: `(false ? 1.0f : (false ? 1.0f : null))` - arg 2 '1.0f' : type float - arg 3 '(false ? 1.0f : null)' : type Float (this expr is same as E1) - therefore, outer operator ?: : type float (see explanation below) - therefore un-autobox arg3 

Explicação detalhada:

Aqui está o meu entendimento de ler as especificações e trabalhar de trás para frente a partir do resultado obtido. Ele se resume ao tipo do terceiro operando da condicional interna f2 é do tipo nulo, enquanto o tipo do terceiro operando da condicional externa f2 é considerado como Flutuante.

Nota: É importante lembrar que a determinação do tipo e a inserção do código de boxe / unboxing é feita em tempo de compilation. A execução real do código de boxing / unboxing é feita em tempo de execução.

 Float f1 = (false ? 1.0f : null); Float f2 = (false ? 1.0f : (false ? 1.0f : null)); 

O condicional f1 e o condicional interno f2: (falso? 1.0f: nulo)

A condicional f1 e a condicional interna f2 são idênticas: (false? 1.0f: null) . Os tipos de operandos na condicional f1 e na condicional interna f2 são:

 type of second operand = float type of third operand = null type (§4.1) 

A maioria das regras em §15.25 são passadas e esta avaliação final é de fato aplicada:

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

 S1 = float S2 = null type T1 = Float T2 = null type type of the f1 and f2 inner conditional expressions = Float 

Como para f1, a atribuição é para uma variável de referência Flutuante, o resultado da expressão (nulo) é atribuído com sucesso.

Para f2 condicional externa: (false? 1.0f: [f2 condicional interna])

Para o condicional externo f2, os tipos são:

 type of second operand = float type of third operand = Float 

Observe a diferença nos tipos de operandos comparados aos condicionais internos f1 / f2 que referenciam o literal nulo diretamente ( §4.1 ). Devido a essa diferença de ter dois tipos de conversíveis numéricos, esta regra de §15.12.2.7 se aplica:

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

    • Caso contrário, a promoção numérica binária ( §5.6.2 ) é aplicada aos tipos de operandos, e o tipo da expressão condicional é o tipo promovido do segundo e terceiro operandos. Observe que a promoção numérica binária executa conversão de unboxing ( §5.1.8 ) e conversão de conjunto de valores ( §5.1.13 ).

Por causa da conversão de unboxing realizada no resultado da condicional interna f2 (nula), um NullPointerException é gerado.

O seguinte lançará um NPE ao tentar atribuir um nulo a um primitivo

  float f1 = false ? 1.0f: null; 

Isso eu acredito é o que está causando o NPE na segunda declaração. Como o primeiro ternário retorna um float para true, ele tenta converter o falso em float também.

A primeira instrução não será convertida em nula, pois o resultado necessário é um Flutuador

Isso, por exemplo, não lançaria um NPE já que não precisa mais converter em primitivo

  Float f = false? new Float(1.0f): true ? null : 1.0f; 

Eu acho que rewrite o código torna a explicação mais clara:

  float f = 1.0f; Float null_Float = false? f : null; // float + null -> OK Float null_Float2 = false? (Float)f : null_Float; // Float + Float -> OK Float npe = false? f : null_Float; // float + Float -> NPE 

Assim, o NPE é quando tentamos fazer algo como:

 Float npe = false? 1.0f : (Float)null; 

Ser ou não ser, essa é a questão. 🙂

Edit: Na verdade, olhando mais de perto, parece que este caso é na verdade uma mistura entre os puzzles de Hamlet (operador ternário e integral integral) e os puzzles Elvis (auto-unboxing null). De qualquer forma, só posso recomendar assistir ao vídeo, é muito educativo e agradável.

Parece que a JVM tenta desmarcar o segundo nulo para flutuar em vez de Flutuar , assim, NullPointerException. Acerte-me uma vez. Minha opinião é que o segundo se faz porque a parte verdadeira do primeiro se avalia como um float, não um Float.

Depois de pensar duas vezes, acho que esse é um jeito do Java dizer que você está fazendo algo estranho. Apenas não aninhe os ifs ternários e você estará bem 🙂