В жизни есть много условностей, например, если тебе горит красный — стой, зелёный — иди. Так и при написании кода — надо обрабатывать много условий. В Java для работы с такими условиями предусмотрено несколько операторов. Наверно, в первую очередь на память приходит оператор if-else
(который так и можно перевести если-иначе). Но есть оператор, который используют не все разработчики, хотя порой он является не менее, а в некоторых ситуациях даже более удобным, чем тот же if-else
. Речь пойдет о тернарном операторе. Рассмотрим его чуть подробнее:
Этот оператор называется тернарным (тройным), так как принимает три операнда. По своей сути он похож на оператор if-else
, но при этом обязательно возвращает значение.
При записи тернарного оператора используются три операнда и два символа ? и :
<условие> ? <выражение 1> : <выражение 2>
то есть в случае, если «условие» == true
, то выполняется «выражение 1»
, если «условие»== false
, то выполняется «выражение 2»
.
Как говорилось вначале — этот оператор по сути аналогичен оператору if-else
и эту же запись можно написать так:
if («условие») «выражение 1»; else «выражение 2»;
Посмотрим на примере:
public int min (int x, int y) { return x < y ? x : y; }
Это же можно было бы написать и с if-else
:
public int min(int x, int y) { if (x < y) return x; else return y; }
Результат работы одинаковый, но запись метода с использованием тернарного оператора более компактна, без ущерба для читаемости.
Использование тернарного оператора не ограничивается выбором из двух вариантов. В качестве третьего операнда можно передавать новый тернарный оператор и так далее. Например (для простоты допустим, что остаток до отпуска не больше года):
public String remVacation(int rem) { return "До отпуска " + rem + (rem == 0 || rem > 4 ? " месяцев" : rem > 1 ? " месяца" : " месяц"); }
На выходе получаем (например, при rem = 4
) — «До отпуска 4 месяца»
Кстати, для читаемости «условие» в тернарном операторе можно брать в скобки, компилятор примет и такой вариант:
((rem==0 || rem > 4) ? " месяцев" : (rem > 1) ? " месяца" : " месяц")
Эта же запись при использовании if-else
может выглядеть примерно так:
public String remVacationIf(int rem) { String str = "До отпуска "; if (rem == 0 || rem > 4) return str += rem + " месяцев"; else if (rem > 1) return str += rem + " месяца"; else return str += rem + " месяц"; }
Опять же использование тернарного оператора гораздо удобнее из-за компактности кода.
Конечно, если еще увеличивать возможность вариантов, то тогда if-else
наверно будет всё-таки предпочтительней, так как он становится более понятным и легко читаемым, чем тернарный оператор, описывающий все варианты в одной строке. Но при использовании двух или трёх вариантов, тернарный оператор, кажется выигрывает за счет своей компактности.
При всей простоте тернарного оператора всё-равно есть несколько нюансов, которые стоит учитывать. Например, что мы ожидаем на выходе этого метода:
public Number test() { int x = 6; double y = 3.0; return 2 * 2 == 4 ? x : y; }
Кажется, по логике на выходе должен быть x
типа int
который равен 6. И на самом деле на выходе мы получаем x = 6
, но только уже типа double
(x = 6.0
). Оказывается, что хотя при таком условии до операнда y
никогда дело не дойдет, но в тернарном операторе компилятор оценивает оба операнда x
и y
и приводит их к общему типу (в данном случае это double
). Вот почему на выходе мы получили не ожидаемый int
, а double
.
Посмотрим еще один пример с примитивом и классом-оберткой :
public Number test2 () { Integer x = null; int y = 6; return 2*2==4 ? x : y; }
И снова хотелось на выходе увидеть null
, но в данном случае мы получаем ошибку — NullPointerException
. Почему метод не может вернуть x
, равный null
? Всё по той же причине — компилятор оценил оба операнда и привел возвращаемое значение к типу int
, а примитив конечно же не может иметь значение null
.
Произведем небольшое изменение в коде (меняем тип y
на Integer
):
public Number test2() { Integer x = null; Integer y = 6; return 2 * 2 == 4 ? x : y; }
В этот раз метод нормально отработал и вернул null
. В чем причина? Теперь мы уже понимаем, что хотя операнд y
и не принимает в данном случае участия в работе оператора, но может существенно влиять на то, какой тип возвращаемого значения выберет компилятор. В данном случае так как и x
, и y
типа Integer
, то компилятор возвратил объект класса-обертки Integer
, а он в отличие от примитива уже может иметь значение null
.
Описание подобных ситуаций и то, какой тип будет возвращен — описаны в документации Oracle.
Итак, мы разобрались с тем, как работает тернарный оператор, а также с тем что при небольшом «ветвлении» условий его использование даже более предпочтительней, чем оператора if-else
. Также мы поняли важную особенность тернарного оператора — необходимо учитывать, что компилятор анализирует типы обоих операндов, независимо от того, что участвует только один из них и устанавливает какого типа будет возвращаемое значение. Это поможет нам понять какой тип значения мы получим на выходе.
Сергей Малыгин, после окончания базового курса, май 2020