Возврат null как int разрешен с тернарным оператором, но не с оператором if

186

Давайте посмотрим на простой код Java в следующем фрагменте:

public class Main {

    private int temp() {
        return true ? null : 0;
        // No compiler error - the compiler allows a return value of null
        // in a method signature that returns an int.
    }

    private int same() {
        if (true) {
            return null;
            // The same is not possible with if,
            // and causes a compile-time error - incompatible types.
        } else {
            return 0;
        }
    }

    public static void main(String[] args) {
        Main m = new Main();
        System.out.println(m.temp());
        System.out.println(m.same());
    }
}

В этом простейшем Java-коде temp()метод не выдает ошибку компилятора, хотя возвращаемый тип функции - это int, и мы пытаемся вернуть значение null(через оператор return true ? null : 0;). При компиляции это, очевидно, вызывает исключение времени выполнения NullPointerException.

Тем не менее, оказывается , что то же самое , это не так , если мы представляем трехкомпонентный оператор с ifутверждением (как в same()методе), который делает выдает ошибку во время компиляции! Зачем?

лев
источник
6
Кроме того , int foo = (true ? null : 0)и new Integer(null)как скомпилироваться, второе явный вид Autoboxing.
Изката
2
@Izkata проблема здесь для меня , чтобы понять , почему компилятор пытается Autobox , nullчтобы Integer... Это будет выглядеть так же , как «угадывание» мне или «делать вещи работу» ...
Марселлас Wallace
1
... Хм, я думал, что у меня там есть ответ, так как конструктор Integer (то, что, как я обнаружил, документы используются для автобоксинга) может принимать строку в качестве аргумента (который может быть нулевым). Тем не менее, они также говорят, что конструктор действует идентично методу parseInt (), который генерирует исключение NumberFormatException при получении нулевого значения ...
Izkata
3
@Izkata - аргумент String c'or для Integer не является операцией автобоксирования. Строка не может быть автоматически упакована в целое число. (Функция Integer foo() { return "1"; }не скомпилируется.)
Тед Хопп
5
Круто, узнал что-то новое о троичном операторе!
оксайт

Ответы:

118

Компилятор интерпретирует nullкак нулевую ссылку на an Integer, применяет правила автоматической блокировки / распаковки для условного оператора (как описано в спецификации языка Java, 15.25 ) и успешно перемещается дальше. Это создаст NullPointerExceptionво время выполнения, что вы можете подтвердить, попробовав его.

Тед Хопп
источник
Учитывая ссылку на спецификацию языка Java, которую вы разместили, какой момент, по вашему мнению, будет выполнен в случае вопроса выше? Последний один (так как я все еще пытаюсь понять capture conversionи lub(T1,T2)) ?? Кроме того, действительно ли можно применить бокс к нулевому значению? Разве это не похоже на "угадывание" ??
Марселлус Уоллес
´ @ Gevorg Нулевой указатель является допустимым указателем на каждый возможный объект, поэтому ничего плохого там не может произойти. Компилятор просто предполагает, что null - это целое число, которое он затем может автоматически помещать в int.
Во
1
@Gevorg - см. Комментарий Nowaq и мой ответ на его пост. Я думаю, что он выбрал правильный пункт. lub(T1,T2)является наиболее конкретным ссылочным типом, общим в иерархии типов T1 и T2. (У них обоих есть хотя бы один объект, поэтому всегда существует самый специфический тип ссылки.)
Тед Хопп,
8
@Gevorg - nullне упакован в целое число, он интерпретируется как ссылка на целое число (пустая ссылка, но это не проблема). Целочисленный объект не создается из нуля, поэтому нет никаких причин для NumberFormatException.
Тед Хопп
1
@Gevorg - Если вы посмотрите на правила для преобразования бокса и примените их к null(который не является примитивным числовым типом), применимое предложение будет следующим: «Если p является значением любого другого типа, преобразование в бокс эквивалентно преобразованию идентичности ». Таким образом, бокс преобразование nullв Integerвыходы null, без вызова какого-либо Integerконструктора.
Тед Хопп
40

Я думаю, компилятор Java интерпретирует true ? null : 0как Integerвыражение, которое можно неявно преобразовать в int, возможно, дать NullPointerException.

Во втором случае выражение nullимеет специальный нулевой тип see , поэтому код return nullделает несоответствие типов.

Влад
источник
2
Я полагаю, это связано с автобоксом? Предположительно, первое возвращение не скомпилируется до Java 5, верно?
Майкл МакГоуэн
@ Майкл, который, кажется, имеет место, если вы установите уровень соответствия Eclipse до 5.
Джонатон Фауст
@Michael: это определенно похоже на автобокс (я новичок в Java и не могу сделать более четкое заявление - извините).
Влад
1
@Vlad как бы конец компилятор до интерпретации , true ? null : 0как Integer? По автобоксу 0сначала ??
Марселлус Уоллес
1
@Gevorg: посмотрите здесь : в противном случае второй и третий операнды имеют типы S1 и S2 соответственно. Пусть T1 будет типом, который получается в результате применения преобразования в бокс для S1, и пусть T2 будет типом, который получается в результате применения преобразования в бокс в S2. и следующий текст.
Влад
32

На самом деле, все это объясняется в спецификации языка Java .

Тип условного выражения определяется следующим образом:

  • Если второй и третий операнды имеют одинаковый тип (который может быть нулевым типом), то это тип условного выражения.

Поэтому "null" в вашем (true ? null : 0)получает тип int, а затем автоматически упаковывается в Integer.

Попробуйте что-то подобное, чтобы убедиться в этом, (true ? null : null)и вы получите ошибку компилятора.

nowaq
источник
3
Но этот пункт правил не применяется: второй и третий операнды не имеют одинаковый тип.
Тед Хопп
1
Тогда кажется, что ответ заключается в следующем утверждении:> В противном случае второй и третий операнды имеют типы S1 и S2 соответственно. Пусть T1 будет типом, который получается в результате применения преобразования в бокс для S1, и пусть T2 будет типом, который получается в результате применения преобразования в бокс в S2. Тип условного выражения является результатом применения преобразования захвата (§5.1.10) к lub (T1, T2) (§15.12.2.7).
сейчас
Я думаю, что это применимо. Затем он пытается применить автоматическую распаковку, чтобы вернуть intзначение из функции, которая вызывает NPE.
Тед Хопп
@ теперь я тоже так думал. Тем не менее, если вы попытаетесь явно боксировать , nullчтобы Integerс new Integer(null);«Пусть T1 тип , который является результатом применения преобразования по боксу S1 ...» вы получитеNumberFormatException и это не так ...
Марселлас Wallace
@Gevorg Я думаю, так как исключение происходит, когда мы занимаемся боксом, мы не получаем никакого результата здесь. Компилятор просто обязан сгенерировать код, который следует определению, которое он делает - мы просто получаем исключение до того, как закончим.
Во
25

В случае ifоператора nullссылка не рассматривается как Integerссылка, потому что она не участвует в выражении, которое заставляет его интерпретироваться как таковое. Поэтому ошибка может быть легко обнаружена во время компиляции, потому что это более четко ошибка типа .

Что касается условного оператора, спецификация языка Java §15.25 «Условный оператор ? :» хорошо отвечает на это в правилах о том, как применяется преобразование типов:

  • Если второй и третий операнды имеют одинаковый тип (который может быть нулевым типом), то это тип условного выражения.

    Не применяется, потому что nullнет int.

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

    Не применяется потому , что ни nullни intне booleanили Boolean.

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

    Не применяется, потому что nullимеет нулевой тип, но intне является ссылочным типом.

  • В противном случае, если второй и третий операнды имеют типы, которые можно преобразовать (§5.1.8) в числовые типы, то существует несколько случаев: […]

    Применяется:null рассматривается как преобразуемый в числовой тип и определен в §5.1. 8 «Распаковка конверсии», чтобы бросить NullPointerException.
Джон Перди
источник
Если 0он автоматически установлен, Integerто компилятор выполняет последний случай «правил троичного оператора», как описано в Спецификации языка Java. Если это правда, тогда мне трудно поверить, что тогда он перейдет к случаю 3 тех же правил, имеющих нулевой и ссылочный тип, которые делают возвращаемое значение троичного оператора ссылочным типом (целое число). .
Марселлас Wallace
@Gevorg - Почему трудно поверить, что троичный оператор возвращает Integer? Это именно то, что происходит; NPE генерируется при попытке распаковать значение выражения для возврата intиз функции. Измените функцию, чтобы она возвращала, Integerи она вернется nullбез проблем.
Тед Хопп
2
@TedHopp: Геворг отвечал на более ранний пересмотр моего ответа, который был неверным. Вы должны игнорировать несоответствие.
Джон Пурди
@JonPurdy "Говорят, что тип может быть преобразован в числовой тип, если это числовой тип, или это ссылочный тип, который может быть преобразован в числовой тип путем распаковки преобразования", и я не думаю, что он nullпопадает в эту категорию , Кроме того, мы затем перейдем к шагу «В противном случае применяется двоичное числовое продвижение (§5.6.2) ... Обратите внимание, что двоичное числовое продвижение выполняет преобразование без распаковки (§5.1.8) ...», чтобы определить тип возвращаемого значения. Но преобразование без коробки генерирует NPE, и это происходит только во время выполнения, а не при попытке определить тип троичного оператора. Я все еще в замешательстве ..
Марселлус Уоллес
@Gevorg: распаковка происходит во время выполнения. nullРассматривается , как если бы это был тип int, но на самом деле эквивалентно throw new NullPointerException(), что все.
Джон Перди
11

Первое, что нужно иметь в виду, это то, что троичные операторы Java имеют «тип», и именно это компилятор будет определять и учитывать независимо от того, что является действительным / реальным типом второго или третьего параметра. В зависимости от нескольких факторов тип троичного оператора определяется по-разному, как показано в спецификации языка Java 15.26.

В приведенном выше вопросе мы должны рассмотреть последний случай:

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

Это, безусловно, самый сложный случай, когда вы смотрите на применение преобразования захвата (§5.1.10) и больше всего на lub (T1, T2) .

Простым английским языком и после крайнего упрощения мы можем описать процесс как вычисление «Наименьшего общего суперкласса» (да, подумайте о LCM) второго и третьего параметров. Это даст нам троичный оператор «тип». Опять же, то, что я только что сказал, является крайним упрощением (рассмотрим классы, которые реализуют несколько общих интерфейсов).

Например, если вы попробуете следующее:

long millis = System.currentTimeMillis();
return(true ? new java.sql.Timestamp(millis) : new java.sql.Time(millis));

Вы заметите, что результирующий тип условного выражения - java.util.Dateэто «Наименьший общий суперкласс» для пары Timestamp/ Time.

Так как nullможет быть автоматически помещен во что угодно, «Наименьший общий суперкласс» является Integerклассом, и это будет тип возврата условного выражения (тернарный оператор) выше. Тогда возвращаемое значение будет нулевым указателем типаInteger и именно это будет возвращено троичным оператором.

Во время выполнения, когда виртуальная машина Java распаковывает , выбрасывается Integera NullPointerException. Это происходит потому, что JVM пытается вызвать функцию null.intValue(), где nullесть результат автобоксирования.

По моему мнению (и поскольку мое мнение не в спецификации языка Java, многие люди все равно сочтут его неправильным), компилятор плохо справляется с оценкой выражения в вашем вопросе. Учитывая, что вы написали, true ? param1 : param2компилятор должен сразу определить, что nullбудет возвращен первый параметр -, и это должно вызвать ошибку компилятора. Это похоже на то, когда вы пишете, while(true){} etc...и компилятор жалуется на код под циклом и помечает его как Unreachable Statements.

Ваш второй случай довольно прост, и этот ответ уже слишком длинный ...;)

ИСПРАВЛЕНИЕ:

После другого анализа я считаю, что я был неправ, говоря, что nullзначение может быть упаковано / автоматически упаковано для чего угодно. Говоря о классе Integer, явный бокс состоит в вызове new Integer(...)конструктора или, возможно, Integer.valueOf(int i);(я где-то нашел эту версию). Первый бросил бы NumberFormatException(а этого не происходит), а второй просто не имел бы смысла, поскольку не intможет быть null...

Марселлус Уоллес
источник
1
nullВ исходном коде OP является не боксировал. Это работает так: компилятор предполагает, что nullэто ссылка на Integer. Используя правила для тернарных типов выражений, он решает, что все выражение является целочисленным выражением. Затем он генерирует код для автоматической блокировки 1(в случае, если условие оценивается как false). Во время выполнения условие оценивается trueкак выражение null. При попытке вернуть функцию intиз функции nullона распаковывается. Это тогда бросает NPE. (Компилятор может оптимизировать большую часть всего этого.)
Тед Хопп
4

На самом деле, в первом случае выражение может быть оценено, поскольку компилятор знает, что оно должно быть оценено как a Integer, однако во втором случае тип возвращаемого значения ( null) не может быть определен, поэтому его нельзя скомпилировать. Если вы Integerприведете его к , код скомпилируется.

Получить
источник
2
private int temp() {

    if (true) {
        Integer x = null;
        return x;// since that is fine because of unboxing then the returned value could be null
        //in other words I can say x could be null or new Integer(intValue) or a intValue
    }

    return (true ? null : 0);  //this will be prefectly legal null would be refrence to Integer. The concept is one the returned
    //value can be Integer 
    // then null is accepted to be a variable (-refrence variable-) of Integer
}
Youans
источник
0

Как насчет этого:

public class ConditionalExpressionType {

    public static void main(String[] args) {

        String s = "";
        s += (true ? 1 : "") instanceof Integer;
        System.out.println(s);

        String t = "";
        t += (!true ? 1 : "") instanceof String;
        System.out.println(t);

    }

}

Вывод правда, правда.

Цвет Eclipse кодирует 1 в условном выражении как автоматически упакованный.

Я предполагаю, что компилятор видит возвращаемый тип выражения как Object.

Джон
источник