Я немного озадачен об условном операторе. Рассмотрите следующие две строки:
Float f1 = false? 1.0f: null;
Float f2 = false? 1.0f: false? 1.0f: null;
Почему f1 становится пустым, и второй оператор бросает NullPointerException?
Параграф 15.25 Langspec-3.0 Саис:
Иначе вторые и третьи операнды имеют типы S1 и S2 соответственно. Позвольте T1 быть типом, который следует из применения преобразования упаковки в S1, и позвольте T2 быть типом, который следует из применения преобразования упаковки в S2. Тип условного выражения является результатом применения преобразования получения (§5.1.10) к лабу (T1, T2) (§15.12.2.7).
Таким образом для false?1.0f:null
T1 является Плаванием, и T2 является пустым типом. Но каков результат lub(T1,T2)
? Этот параграф 15.12.2.7 просто немного слишком много...
BTW, я использую 1.6.0_18 в Windows.
PS: Я знаю это Float f2 = false? (Float) 1.0f: false? (Float) 1.0f: null;
не бросает NPE.
Разница заключается в статической типизации выражений во время компиляции:
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 inner - null type, а тип третьего операнда условия f2 outer считается Float.
Примечание: Важно помнить, что определение типа и вставка кода boxing/unboxing выполняется во время компиляции. Фактическое выполнение кода boxing/unboxing происходит во время выполнения.
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 присвоение производится ссылочной переменной Float, результат выражения (null) успешно присвоен.
Для f2 внешнее условие: (false ? 1.0f : [внутреннее условие f2])
Для внешнего условия f2 типы:
type of second operand = float
type of third operand = Float
Обратите внимание на разницу в типах операндов по сравнению с внутренними условиями f1/f2, которые ссылаются на литерал null напрямую (§4.1). Из-за этого отличия наличия двух числовых конвертируемых типов применяется правило из §15.12.2.7:
Иначе, если второй и третий операнды имеют конвертируемые типы (§5.1. 8) к числовым типам, то возможны несколько случаев: ...
- В противном случае к типам операндов применяется двоичное числовое продвижение (§5.6.2), и типом условного выражения является продвинутый тип второго и третьего операндов. Обратите внимание, что двоичное числовое продвижение выполняет преобразование без упаковки (§5.1.8) и преобразование набора значений (§5.1.13).
Из-за того, что для результата внутреннего условия f2 (null) было выполнено преобразование unboxing, возникло исключение NullPointerException.
Я думаю, что переписывание кода делает объяснение более ясным:
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;
Быть или не быть - вот в чем вопрос. :)
Edit: На самом деле, если присмотреться, кажется, что этот случай на самом деле представляет собой смесь между Hamlet (тернарный оператор и обернутые целочисленные типы) и Elvis (автоматическая распаковка нуля) головоломки. В любом случае могу только рекомендовать посмотреть видео, оно очень познавательное и приятное.
Следующий пример вызовет ошибку NPE, поскольку вы пытаетесь присвоить null примитиву
float f1 = false ? 1.0f: null;
Я полагаю, что именно это и вызывает ошибку NPE во втором операторе. Поскольку первая тройка возвращает float для true, она также пытается преобразовать false в float.
Первый оператор не будет конвертировать в null, так как требуемый результат - Float
Это, например, не вызовет NPE, так как больше не нужно конвертировать в примитив
Float f = false? new Float(1.0f): true ? null : 1.0f;
Похоже, что JVM пытается распаковывать второе значение null в float вместо Float, таким образом, NullPointerException. Ударил его сам один раз. Я так считает, что вторая часть if делает это, потому что часть true первой if оценивается как float, а не Float.
Подумав, я думаю, что это способ Java сказать вам, что вы делаете что-то странное. Только не гнездите троичные если, и у вас все будет хорошо :-)