Tricky trernary operator в Java - автобоксинг

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

178
задан Lion 11 нояб. '11 в 22:30
источник поделиться

8 ответов

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

109
ответ дан Ted Hopp 11 нояб. '11 в 22:36
источник поделиться

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

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

39
ответ дан Vlad 11 нояб. '11 в 22:32
источник поделиться

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

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

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

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

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

32
ответ дан nowaq 11 нояб. '11 в 22:50
источник поделиться

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

Как и для условного оператора, спецификация языка Java §15.25 "Условный оператор ? :" прекрасно отвечает на правила использования преобразования типов:

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

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

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

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

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

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

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

    Применяется: null рассматривается как преобразуемый в числовой тип и определяется в п. 5.1.1. "Преобразование распаковки", чтобы выбросить NullPointerException.
24
ответ дан Jon Purdy 11 нояб. '11 в 23:43
источник поделиться

Первое, что нужно иметь в виду, это то, что 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...

11
ответ дан Gevorg 12 нояб. '11 в 9:57
источник поделиться

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

4
ответ дан GeT 15 нояб. '11 в 23:09
источник поделиться
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
}
2
ответ дан YouYou 10 дек. '13 в 23:52
источник поделиться

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

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.

0
ответ дан Jon 08 сент. '16 в 22:40
источник поделиться

Другие вопросы по меткам