Как работает распаковка в укороченных логических выражениях?

Недавно я попытался запустить следующие два фрагмента кода и был удивлен результатом.

Первый:

// ...
System.out.println( (Boolean)null || true );
// ...

Второй:

// ...
System.out.println( (Boolean)null || false );
// ...

Первый пример приводит к следующему выводу:
true

Второй пример приводит к следующему выводу:
Исключение в потоке "main" java.lang.NullPointerException
в com.blah.main(SanityCheck.java:26)

Я бы подумал, что оба примера должны привести к исключению нулевого указателя, поскольку любое короткое замыкание применяется слева направо. Попытка распаковать логическое значение из логического значения должна была потерпеть неудачу до того, как будет рассмотрена другая сторона логики.

Кто-нибудь может объяснить это непоследовательное поведение?


person Reinstate Monica 2331977    schedule 23.08.2013    source источник
comment
Такое поведение несовместимо с JLS Во время выполнения сначала вычисляется выражение левого операнда; если результат имеет тип Boolean, он подвергается распаковке   -  person Patricia Shanahan    schedule 23.08.2013


Ответы (3)


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

Boolean — это объект, поэтому его можно установить равным нулю. Это не то место, где выбрасывается исключение. Исключение NullPointerException возникает, когда вы пытаетесь выполнить операцию над логическим объектом, для которого задано значение null. В истинном случае компилятор передаст приведение null к логическому значению, а поскольку операция ИЛИ с истинным всегда будет давать истину, условное выражение будет истинным. В случае false компилятор снова передаст приведение null к логическому значению, затем он проверит false, и, если условие ложно, ему необходимо вычислить OR с логическим значением, потому что условие в конечном итоге может оказаться истинным или ложным. Когда происходит вычисление, генерируется исключение NullPointerException.

person user1549672    schedule 23.08.2013
comment
Это хороший ответ, но я думал, что любое короткое замыкание происходит слева направо. Если левый операнд истинно в или, правый операнд не оценивается. Точно так же, если левый операнд является истинным в операторе и, правый операнд не оценивается. Я предполагаю, что это может быть некоторая оптимизация компилятора, поэтому я попытаюсь декомпилировать файл класса. - person Reinstate Monica 2331977; 23.08.2013
comment
На самом деле я думаю, что неправильно понял то, что вы написали - короткое замыкание даже не применяется, потому что оно оптимизируется во время компиляции. Точно. - person Reinstate Monica 2331977; 23.08.2013
comment
Правильный! Кастинговые пасы и истинные пасы. Тогда нет необходимости в истинном ИЛИ *, потому что это всегда будет правдой - person user1549672; 23.08.2013
comment
Важно отметить, что приведение null к Boolean не создает исключение NullPointerException. Выдает ли первый пример исключение NullPointerException, по-видимому, зависит от реализации JDK (на основе ответов ниже). - person jahroy; 23.08.2013

Я прогнал файл класса через JAD, чтобы посмотреть, как выглядит оптимизированный код. Для истинного случая:

// ...
System.out.println(true);
// ...

Для ложного случая:

// ...
System.out.println(null.booleanValue());
// ...
person Reinstate Monica 2331977    schedule 23.08.2013

Я не могу воспроизвести ваши результаты. Я фактически получаю NPE для обоих случаев.

Согласно JLS, выражение левого операнда всегда оценивается первым. Когда оценивается (Boolean)null, выполняется автоматическая распаковка нулевого логического объекта. В частности, основной код null.booleanValue() вызывает NPE.

person Terry Li    schedule 23.08.2013
comment
Вы не должны получать NPE для истинного случая. (Boolean)null отлично работает сам по себе. Даже если он вычисляется первым, он не должен создавать исключение NullPointerException. - person user1549672; 23.08.2013
comment
@user1549672 user1549672 Это действительно происходит со мной, поэтому я все еще пытаюсь понять проблему. Зависит ли результат от версии JDK, которую я использую? - person Terry Li; 23.08.2013
comment
Я не уверен. Какую версию вы используете? - person user1549672; 23.08.2013
comment
@user1549672 user1549672 Я использую JDK по умолчанию в комплекте с Netbeans 7.3. - person Terry Li; 23.08.2013
comment
@ user1549672 Я подозреваю, что это результат оптимизации компилятора. В истинном случае выражение выполняется во время компиляции, поэтому во время выполнения не выдается NPE. Я считаю, что моя версия компилятора не поддерживает такую ​​оптимизацию. - person Terry Li; 23.08.2013
comment
Хм, я думаю, это может быть разница в версиях JDK. Может быть стоит изучить! - person user1549672; 23.08.2013
comment
@TerryLi - вот мое мнение: приведение нулевого значения к логическому значению не вызывает NPE, поэтому наблюдаемая вами разница должна быть связана с различными оптимизациями, выполняемыми разными реализациями компилятора (например, OpenJDK против Oracle JDK). - person jahroy; 23.08.2013
comment
Я использую javac 1.7.0_03 и java 1.7.0_25-b17. - person Reinstate Monica 2331977; 23.08.2013
comment
у меня тоже сборка 1.7 - person user1549672; 23.08.2013
comment
Я также получаю NPE для обоих случаев. Я использую Sun JDK 1.6.0_21-b06 на Ubuntu 12.04 (установлен несколько лет назад). - person jahroy; 23.08.2013
comment
Если бы мне пришлось угадывать, я бы сказал, что реализация важнее, чем номер версии. - person jahroy; 23.08.2013
comment
@jahroy Я согласен с вами, что реализация имеет значение. - person Terry Li; 23.08.2013