Хитрый тернарный оператор в 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 ответов:
Компилятор интерпретирует
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
ANullPointerException
выбрасывается. Это происходит потому, что 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.
Я предполагаю, что компилятор видит возвращаемый тип выражения как объект.