Возврат null в виде int разрешен с тернарным оператором, но не в операторе if - PullRequest
185 голосов
/ 11 ноября 2011

Давайте посмотрим на простой код 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()), который действительно выдает ошибку во время компиляции! Почему?

Ответы [ 8 ]

115 голосов
/ 11 ноября 2011

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

39 голосов
/ 11 ноября 2011

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

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

32 голосов
/ 11 ноября 2011

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

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

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

Следовательно, «null» в вашем (true ? null : 0) получает тип int, а затем автоматически упаковывается в Integer.

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

25 голосов
/ 12 ноября 2011

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

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

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

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

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

    Не применяется, поскольку ни null, ни int не являются boolean или Boolean.

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

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

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

    Применяется: null обрабатывается как конвертируемый в числовой тип и определяется в §5.1.8 «Распаковка преобразования» для броска NullPointerException.
11 голосов
/ 12 ноября 2011

Первое, что нужно иметь в виду, это то, что троичные операторы 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 распаковывает, Integer a 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 ...

4 голосов
/ 16 ноября 2011

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

2 голосов
/ 11 декабря 2013
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
}
0 голосов
/ 08 сентября 2016

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

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);

    }

}

Вывод true, true.

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

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

...