Хитрый тернарный оператор в Java-autoboxing


Давайте рассмотрим простой 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 182

8 ответов:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    применяется: null рассматривается как конвертируемый в числовой тип и определяется в §5.1.8 "преобразование распаковки", чтобы бросить NullPointerException.

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

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

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

Это, безусловно, самый сложный случай, если вы взглянете на применение преобразования захвата (§5.1.10) и больше всего при луб(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...

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

private int temp() {

    if (true) {
        Integer x = null;
        return x;// since that is fine because of auto-boxing 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
}

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

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 color кодирует 1 в условном выражении как autoboxed.

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