Условный оператор Java?: Тип результата - PullRequest
36 голосов
/ 11 апреля 2010

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

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

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

Langspec-3.0 пункт 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.

Ответы [ 5 ]

26 голосов
/ 11 апреля 2010

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

Краткое описание

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 нулевого типа, в то время как тип третьего операнда условного выражения f2 external считается 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 присваивание является ссылочной переменной 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), возникает исключение NullPointerException.

2 голосов
/ 11 апреля 2010

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

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

2 голосов
/ 11 апреля 2010

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

    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;
2 голосов
/ 11 апреля 2010

Следующее бросит NPE, когда вы попытаетесь присвоить примитиву ноль

    float f1 = false ? 1.0f: null;

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

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

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

    Float f = false? new Float(1.0f): true ? null : 1.0f;
1 голос
/ 11 апреля 2010

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

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

...