Booleanos, operadores condicionales y autoboxing
-
28-09-2019 - |
Pregunta
¿Por qué este NullPointerException
tiro
public static void main(String[] args) throws Exception {
Boolean b = true ? returnsNull() : false; // NPE on this line.
System.out.println(b);
}
public static Boolean returnsNull() {
return null;
}
Si bien esto no hace
public static void main(String[] args) throws Exception {
Boolean b = true ? null : false;
System.out.println(b); // null
}
La solución es por la forma de reemplazar false
por Boolean.FALSE
a null
evitar ser sin embalaje a boolean
-que no es posible. Pero eso no es la cuestión. La pregunta es ¿Por qué ? ¿Hay referencias en JLS lo que confirma este comportamiento, especialmente del segundo caso?
Solución
La diferencia es que el tipo explícita del método returnsNull()
afecta a la tipificación estática de las expresiones en tiempo de compilación:
E1: `true ? returnsNull() : false` - boolean (auto-unboxing 2nd operand to boolean)
E2: `true ? null : false` - Boolean (autoboxing of 3rd operand to Boolean)
Vea Especificación del lenguaje Java, la sección 15.25 operador condicional? :
-
Para E1, los tipos de la 2ª y 3ª operandos son
Boolean
yboolean
respectivamente, por lo que se aplica esta cláusula:Si uno de los segundo y tercer operandos es de tipo booleano y el tipo de la otra es de tipo booleano, entonces el tipo de la expresión condicional es booleano.
Dado que el tipo de la expresión es
boolean
, el segundo operando debe ser obligado aboolean
. El compilador inserta código de auto-unboxing del segundo operando (valor de retorno dereturnsNull()
) para que sea el tipoboolean
. Por supuesto, esto hace que la NPE de lanull
devuelto en tiempo de ejecución. -
Para E2, tipos de la 2ª y 3ª operandos son
<special null type>
(noBoolean
como en E1!) Yboolean
respectivamente, por lo que no se aplica la cláusula específica de escribir ( ir a leer 'em ), por lo que el final! "lo contrario" se aplica la cláusula:De lo contrario, el segundo y tercer operandos son de tipos de S1 y S2 respectivamente. Deje que T1 sea del tipo que resulta de aplicar la conversión de boxeo a S1, y dejar T2 sean del tipo que resulta de aplicar la conversión de boxeo a S2. El tipo de la expresión condicional es el resultado de aplicar la conversión de captura (§5.1.10) para lub (T1, T2) (§15.12.2.7).
- S1 ==
<special null type>
(ver § 4.1 ) - S2 ==
boolean
- T1 == caja (S1) ==
<special null type>
(véase el último elemento de la lista de conversiones de boxeo en §5.1.7 ) - caja T2 == (S2) == `Boolean
- lub (T1, T2) ==
Boolean
Así que el tipo de la expresión condicional es
Boolean
y la tercera operando debe ser obligado aBoolean
. El compilador inserta código de auto-boxing para la tercera operando (false
). El segundo operando no necesita la auto-unboxing como enE1
, por lo que no NPE auto-unboxing cuando se devuelvenull
. - S1 ==
Esta pregunta requiere un análisis tipo similar:
Otros consejos
La línea:
Boolean b = true ? returnsNull() : false;
se transforma internamente a:
Boolean b = true ? returnsNull().booleanValue() : false;
para realizar la unboxing; así: null.booleanValue()
producirá un NPE
Este es uno de los escollos más importantes cuando se utiliza autoboxing. Este comportamiento es, en efecto documentado en 5.1.8 JLS
Edit: Creo que el unboxing se debe a la tercera operador ser de tipo booleano, como (conversión implícita añadido):
Boolean b = (Boolean) true ? true : false;
Especificación del lenguaje Java, la sección 15.25 :
- Si una de la segunda y tercera operandos es de tipo booleano y el tipo del otro es de tipo booleano, entonces el tipo del condicional expresión es booleano.
Por lo tanto, el primer ejemplo trata de llamar Boolean.booleanValue()
con el fin de convertir a Boolean
boolean
según la primera regla.
En el segundo caso, el primer operando es del tipo null, cuando el segundo no es del tipo de referencia, por lo que la conversión autoboxing se aplica:
- lo contrario, la segunda y tercera operandos son de tipos de S1 y S2 respectivamente. Deje que T1 sea del tipo que resulta de aplicar el boxeo la conversión a S1, y dejar que sea el T2 tipo que resulte de aplicar el boxeo la conversión a S2. El tipo de la expresión condicional es el resultado de aplicar la conversión de captura (§5.1.10) para lub (T1, T2) (§15.12.2.7).
Podemos ver este problema desde el código de bytes. En la línea 3 del código de bytes de principal, 3: invokevirtual #3 // Method java/lang/Boolean.booleanValue:()Z
, el boxeo booleana de valor nulo, el método invokevirtual
java.lang.Boolean.booleanValue
, arrojará NPE, por supuesto.
public static void main(java.lang.String[]) throws java.lang.Exception;
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: invokestatic #2 // Method returnsNull:()Ljava/lang/Boolean;
3: invokevirtual #3 // Method java/lang/Boolean.booleanValue:()Z
6: invokestatic #4 // Method java/lang/Boolean.valueOf:(Z)Ljava/lang/Boolean;
9: astore_1
10: getstatic #5 // Field java/lang/System.out:Ljava/io/PrintStream;
13: aload_1
14: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
17: return
LineNumberTable:
line 3: 0
line 4: 10
line 5: 17
Exceptions:
throws java.lang.Exception
public static java.lang.Boolean returnsNull();
descriptor: ()Ljava/lang/Boolean;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: aconst_null
1: areturn
LineNumberTable:
line 8: 0