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?

¿Fue útil?

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 y boolean 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 a boolean. El compilador inserta código de auto-unboxing del segundo operando (valor de retorno de returnsNull()) para que sea el tipo boolean. Por supuesto, esto hace que la NPE de la null devuelto en tiempo de ejecución.

  • Para E2, tipos de la 2ª y 3ª operandos son <special null type> (no Boolean como en E1!) Y boolean 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 a Boolean. El compilador inserta código de auto-boxing para la tercera operando (false). El segundo operando no necesita la auto-unboxing como en E1, por lo que no NPE auto-unboxing cuando se devuelve null.


Esta pregunta requiere un análisis tipo similar:

Java operador condicional: tipo de resultado

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
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top