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()
), который вызывает ошибку времени компиляции! Почему?
Компилятор интерпретирует null
как нулевую ссылку на Integer
, применяет правила autoboxing/unboxing для условного оператора (как описано в Java Language Specification, 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, тогда тип условного выражения является логическим.
Не применяется, потому что ниnull
, ниint
не являетсяboolean
илиboolean
.
- Если один из второго и третьего операндов имеет нулевой тип и тип другой - ссылочный тип, то тип условного выражения таков: ссылочный тип.
Не применяется, посколькуnull
имеет нулевой тип, ноint
не является ссылочным типом.
- В противном случае, если второй и третий операнды имеют типы, которые могут быть конвертируемыми (§5.1.8) на числовые типы, то есть несколько случаев: [...]
Применяется:null
рассматривается как преобразуемый в числовой тип и определяется в п. 5.1.1. "Преобразование распаковки", чтобы выброситьNullPointerException
.
Первое, что нужно иметь в виду, это то, что 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
...
Собственно, в первом случае выражение может быть оценено, поскольку компилятор знает, что он должен быть оценен как 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);
}
}
Вывод true, true.
Цвет Eclipse кодирует 1 в условном выражении как автобокс.
Мое предположение заключается в том, что компилятор видит возвращаемый тип выражения как Object.
Другие вопросы по меткам java nullpointerexception conditional-operator autoboxing или Задайте вопрос