Тернарный оператор

В жизни есть много условностей, например, если тебе горит красный — стой, зелёный — иди. Так и при написании кода — надо обрабатывать много условий. В 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