119

Почему это бросок NullPointerExceptionBOOLEANS, условные операторы и Autoboxing

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; 
} 

в то время как это не

public static void main(String[] args) throws Exception { 
    Boolean b = true ? null : false; 
    System.out.println(b); // null 
} 

?

Решение по пути замены false на Boolean.FALSE, чтобы избежать null быть распакованный к boolean --which не представляется возможным. Но это не вопрос. Вопрос почему? Есть ли ссылки в JLS, которые подтверждают это поведение, особенно второго случая?

+27

wow, autoboxing - это бесконечный источник ... er ... сюрпризов для java-программиста, не так ли? :-) – leonbloy

+0

У меня была аналогичная проблема, и я удивился тому, что она потерпела неудачу на VM OpenJDK, но работала на HotSpot VM ... Напишите один раз, бегите куда угодно! – kodu

ответ

85

Разница заключается в том, что явный вид метода returnsNull() влияет на статическую типизацию выражений во время компиляции: Спецификация

E1: `true ? returnsNull() : false` - boolean (auto-unboxing 2nd operand to boolean) 

E2: `true ? null : false` - Boolean (autoboxing of 3rd operand to Boolean) 

См языка Java, раздел 15.25 Conditional Operator ? :

  • Для E1 типы 2-го и 3-го операндов Boolean и boolean соответственно, так что это положение применимо:

    Если один из второго и третьих операндов имеет типа логического значения и типа другого типа Boolean, то тип условного выражения является логическим значением.

    Поскольку тип выражения boolean, 2-й операнд должен быть приведен к boolean. Компилятор вставляет код авто-распаковки во второй операнд (возвращаемое значение returnsNull()), чтобы сделать его типом boolean. Это, конечно, вызывает NPE от null, возвращенного во время выполнения.

  • Для Е2, типы 2-го и 3-го операнда <special null type> (не Boolean как в Е1!) И boolean соответственно, так что не конкретное положение типирование не применяется (go read 'em!), поэтому применяется окончательное «иначе» оговорка:

    В противном случае второй и третий операнды имеют типы S1 и S2 соответственно. Пусть T1 является типом, который возникает в результате применения преобразования бокса в S1, и пусть T2 является типом, который возникает в результате применения преобразования бокса в S2. Тип условного выражения является результатом применения преобразования захвата (§ 5.1.10) в lub (T1, T2) (§15.12.2.7).

    • S1 == <special null type> (см §4.1)
    • S2 == boolean
    • T1 == коробка (S1) == <special null type> (см последний элемент в списке переходов бокса в §5.1.7)
    • Т2 == окно (S2) == `Логическое
    • LUB (Т1, Т2) == Boolean

    Таким образом, тип условного выражения равен Boolean, а третий операнд должен быть принужден к Boolean. Компилятор вставляет код авто-бокса для 3-го операнда (false). Второй операнд не нуждается в автоматическом распаковке, как в E1, поэтому нет автоматического unboxing NPE при возврате null.


Этот вопрос нужен подобный анализ типа:

Java conditional operator ?: result type

+4

Делает смысл ... думаю. [§15.12.2.7] (http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#341287) является болью. – BalusC

+0

Это легко ... но только в ретроспективе. :-) –

+0

@BertF Что означает функция 'lub' в' lub (T1, T2) '? – Geek

21

Линия:

Boolean b = true ? returnsNull() : false; 

внутренне преобразуется в:

Boolean b = true ? returnsNull().getBoolean() : false; 

для выполнения распаковки; таким образом: null.getBoolean() даст NPE

Это одна из основных проблем при использовании автобоксинга. Это поведение материала действительно документировано в 5.1.8 JLS

Edit: Я считаю, что распаковка происходит из-за третий оператор бытия логического типа, как (неявный бросок добавлено):

Boolean b = (Boolean) true ? true : false; 
+1

Почему он пытается сделать это с помощью unbox, когда конечное значение является булевым объектом? –

15

От Java Language Specification, section 15.25:

  • Если один из второго и третьего операндов имеет тип boolean и тип другого типа типа Boolean, then th e тип условного выражения является логическим.

Итак, первый пример пытается вызвать Boolean.booleanValue(), чтобы преобразовать Boolean в boolean в соответствии с первым правилом.

Во втором случае первого операнд имеет нулевой типа, когда второй не эталонный типа, поэтому Autoboxing преобразования применяется:

  • В противном случае, вторые и третий операндов типов S1 и S2 соответственно. Пусть T1 - тип, который является результатом применения бокса преобразования в S1, а T2 - , который является результатом применения бокса преобразования в S2. Тип условного выражения является результатом применения преобразования захвата (§5.1.10) в lub (T1, T2) (§15.12.2.7).
+0

Это отвечает на первый случай, но не на второй случай. – BalusC

+0

Возможно, существует исключение, когда одно из значений - «null». –

+0

@Erick: подтверждает ли JLS это? – BalusC

0

Мы видим эту проблему с байт-код. В строке 3 основного байтового кода 3: invokevirtual #3 // Method java/lang/Boolean.booleanValue:()Z бокс с логическим значением 0, invokevirtual метод java.lang.Boolean.booleanValue, он будет бросать NPE, конечно.

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