Булевы, условные операторы и автобоксинг

Почему этот бросок NullPointerException

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 - что невозможно. Но это не вопрос. Вопрос в том, почему? Есть ли ссылки в JLS, которые подтверждают это поведение, особенно 2-го случая?

Ответ 1

Разница в том, что явный тип метода 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 Условный оператор? :

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

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

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

  • Для E2 типы 2-го и 3-го операндов <special null type> (не Boolean как в E1!) И 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 == box (S1) == <special null type> (см. Последний пункт в списке преобразований бокса в §5.1.7)
    • T2 == box (S2) == 'Boolean
    • lub (T1, T2) == Boolean

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


Этот вопрос нуждается в аналогичном анализе:

Java условный оператор?: Тип результата

Ответ 2

Линия:

    Boolean b = true ? returnsNull() : false;

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

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

выполнить распаковку; таким образом: null.booleanValue() даст NPE

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

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

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

Ответ 3

Из Спецификация языка Java, раздел 15.25:

  • Если один из второго и третьего операнды имеют тип boolean и тип другого имеет тип Boolean, то тип условного выражение булево.

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

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

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

Ответ 4

Мы можем видеть эту проблему из байтового кода. В строке 3 основного байтового кода 3: invokevirtual #3 // Method java/lang/Boolean.booleanValue:()Z бокс с логическим значением null, 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