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

Я немного озадачен условным оператором. Рассмотрим следующие две строки:

Float f1 = false? 1.0f: null;
Float f2 = false? 1.0f: false? 1.0f: null;

Почему f1 становится нулевым, а второй оператор генерирует исключение NullPointerException?

Langspec-3.0 para 15.25 sais:

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

Итак, для false?1.0f:null T1 - Float, а T2 - нулевой тип. Но каков результат lub(T1,T2)? Этот параграф 15.12.2.7 просто слишком много...

Кстати, я использую 1.6.0_18 в Windows.

PS: Я знаю, что Float f2 = false? (Float) 1.0f: false? (Float) 1.0f: null; не бросает NPE.

Ответ 1

Разница заключается в статической типизации выражений во время компиляции:

Резюме

E1: `(false ? 1.0f : null)`
    - arg 2 '1.0f'           : type float,
    - arg 3 'null'           : type null 
    - therefore operator ?:  : type Float (see explanation below)
    - therefore autobox arg2
    - therefore autobox arg3

E2: `(false ? 1.0f : (false ? 1.0f : null))`
    - arg 2 '1.0f'                    : type float
    - arg 3 '(false ? 1.0f : null)'   : type Float (this expr is same as E1)
    - therefore, outer operator ?:    : type float (see explanation below)
    - therefore un-autobox arg3

Подробное объяснение:

Здесь я понимаю, что прочитал спецификацию и отработал назад от полученного результата. Оно сводится к типу третьего операнда внутреннего условия f2 - это нулевой тип, тогда как тип третьего операнда внешнего условия f2 считается Float.

Примечание. Важно помнить, что определение типа и вставка кода бокса/распаковки выполняется во время компиляции. Фактическое выполнение кода бокса/распаковки выполняется во время выполнения.

Float f1 = (false ? 1.0f : null);
Float f2 = (false ? 1.0f : (false ? 1.0f : null));

Условие f1 и внутреннее условие f2: (false? 1.0f: null)

Условие f1 и внутреннее условие f2 идентичны: (false? 1.0f: null). Типы операндов в условном условии f1 и внутреннем условии f2:

type of second operand = float
type of third operand = null type (§4.1)

Большинство правил в §15.25 переданы и эта окончательная оценка действительно применяется:

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

S1 = float
S2 = null type
T1 = Float
T2 = null type
type of the f1 and f2 inner conditional expressions = Float

Так как для f1, присвоение опорного Поплавка переменных, результат выражения (нуль) успешно назначен.

Для f2 external conditional: (false? 1.0f: [f2 internal conditional])

Для внешнего условия f2 типы:

type of second operand = float
type of third operand = Float

Обратите внимание на разницу в типах операндов по сравнению с внутренними условиями f1/f2, которые напрямую ссылаются на нулевой литерал (§4.1). Из-за этой разницы, связанной с наличием двух типов числовых конвертируемых, это правило из §15.12.2.7 применяется:

  • В противном случае, если второй и третий операнды имеют типы, которые конвертируются (§5.1.8) в числовые типы, тогда существует несколько случаев:...

    • В противном случае для типов операндов применяется двоичное числовое продвижение (§5.6.2), а тип условного выражения - продвинутого типа второго и третьего операндов. Обратите внимание, что бинарное числовое продвижение выполняет распаковывание преобразования (§5.1.8) и преобразование значений (§5.1.13).

Из-за преобразования unboxing, выполняемого с результатом внутреннего условия f2 (null), возникает исключение NullPointerException.

Ответ 2

Ниже вы будете бросать NPE при попытке присвоить null примитиву

    float f1 = false ? 1.0f: null;

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

Первый оператор не будет преобразовываться в нуль, поскольку требуемым результатом является Float

Это, например, это не вызовет NPE, поскольку его больше не нужно преобразовывать в примитивный

    Float f = false? new Float(1.0f): true ? null : 1.0f;

Ответ 3

Я думаю, что переписывание кода делает объяснение понятным:

    float f = 1.0f;

    Float null_Float  = false?        f  : null;       // float + null  -> OK
    Float null_Float2 = false? (Float)f  : null_Float; // Float + Float -> OK
    Float npe         = false?        f  : null_Float; // float + Float -> NPE

Таким образом, NPE, когда мы пытаемся сделать что-то вроде:

Float npe = false? 1.0f : (Float)null;

Ответ 4

Быть или не быть, вот в чем вопрос.:)

Edit: На самом деле, если посмотреть, кажется, что этот случай представляет собой смесь между Hamlet (тройной оператор и завернутые интегральные типы) и Elvis (авто-unboxing null) головоломки. В любом случае, я могу только рекомендовать просмотр видео, это очень образовательный и приятный.

Ответ 5

Похоже, JVM пытается отменить второй null до float вместо Float, таким образом, NullPointerException. Ударь его сам однажды. Я считаю, что второй, если это так, потому что истинная часть первой, если оценивается как float, а не Float.

После второй мысли я думаю, что это способ Java, говорящий вам, что вы делаете что-то странное. Просто не гнездись тройной ifs, и все будет хорошо: -)