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

Почему это бросок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 ссылки, подтверждающие это поведение, особенно во втором случае?

 leonbloy07 окт. 2010 г., 16:01
вау, автобокс является бесконечным источником ... э ... сюрпризов для Java-программиста, не так ли? :-)
 kodu08 дек. 2017 г., 16:58
У меня была похожая проблема, и меня удивило то, что она не работала на виртуальной машине OpenJDK, но работала на виртуальной машине HotSpot ... Пиши один раз, беги куда угодно!

Ответы на вопрос(4)

Спецификация языка 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).
 Erick Robertson07 окт. 2010 г., 16:07
Я стою исправлено.
 Erick Robertson07 окт. 2010 г., 15:49
Да. Это в следующей строке в приведенной ссылке @axtavt. Я обновил ответ.
 BalusC07 окт. 2010 г., 15:43
Это отвечает на первый случай, но не на второй случай.
 Erick Robertson07 окт. 2010 г., 15:44
Вероятно, есть исключение, когда одно из значенийnull.
 axtavt07 окт. 2010 г., 15:51
@Erick: я не думаю, что это применимо сboolean не является ссылочным типом.
 BalusC07 окт. 2010 г., 15:45
@Erick: JLS подтверждает это?
 Jay07 окт. 2010 г., 16:08
И могу я добавить ... вот почему вы должны сделать обе стороны троичного одинакового типа, с явными вызовами, если это необходимо. Даже если у вас есть запомненные спецификации и вы знаете, что произойдет, следующий программист, который придет и прочитает ваш код, может этого не сделать. По моему скромному мнению, было бы лучше, если бы компилятор просто выдавал сообщение об ошибке в этих ситуациях, а не делал вещи, которые обычным смертным было бы трудно предсказать. Ну, может быть, есть случаи, когда поведение действительно полезно, но я еще не достиг ни одного.
Решение Вопроса

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

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

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

См. Спецификацию языка Java, раздел15.25 Условный оператор? :

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

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

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

Для E2 типы 2-го и 3-го операндов<special null type> (неBoolean как в Е1!) иboolean соответственно, не применяется конкретное предложение о типизации (иди читай их!), поэтому применяется последнее условие «иначе»:

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

S1 ==<special null type> (увидетьп.4.1)S2 ==booleanT1 == коробка (S1) ==<special null type> (см. последний пункт в списке конверсий бокса в§5.1.7)T2 == box (S2) == `Booleanlub (T1, T2) ==Boolean

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

Этот вопрос требует аналогичного анализа типа:

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

 BalusC07 окт. 2010 г., 16:13
Имеет смысл ... я думаю.§15.12.2.7 это боль.
 Geek25 апр. 2014 г., 08:01
@BertF Что делает функцияlub вlub(T1,T2) стоять за?
 Bert F25 апр. 2014 г., 15:03
@Geek - lub () - наименьшая верхняя граница - в основном ближайший суперкласс, который у них общий; поскольку null (тип «специальный нулевой тип») может быть неявно преобразован (расширен) в любой тип, вы можете считать специальный нулевой тип «суперклассом» любого типа (класса) для целей lub ().
 Bert F07 окт. 2010 г., 16:16
Это легко ... но только задним числом. :-)

ода main,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

Линия:

    Boolean b = true ? returnsNull() : false;

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

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

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

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

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

   Boolean b = (Boolean) true ? true : false; 
 Erick Robertson07 окт. 2010 г., 15:41
Почему он пытается так распаковать, когда конечным значением является логический объект?

Ваш ответ на вопрос