Логические операторы в Java
Знакомимся с каждым, узнаём про короткую и полную схемы вычислений. Проводим побитовые операции с целыми числами. Всё закрепляем на примерах.
Логические операции в Java возвращают значение типа boolean: true или false («правда» или «ложь»). Подробнее о булевом типе мы говорили здесь.
В языке Java есть шесть логических операторов. Первые четыре представлены в таблице ниже.
Логический оператор | Обозначение в Java | Выражение | Результат |
---|---|---|---|
«И» (AND): конъюнкция, логическое умножение | && | true && true false && false true && false false && true | true false false false |
Включающее «ИЛИ» (OR): дизъюнкция, логическое сложение | || | true || true false || false true || false false || true | true false true true |
Исключающее «ИЛИ» (XOR): строгая дизъюнкция, логическое вычитание | ^ | true ^ true false ^ false true ^ false false ^ true | false false true true |
«НЕ» (NOT): инверсия, отрицание | ! | !true !false | false true |
Где нужны логические операторы
Если коротко, то в условных выражениях, которые могут включать в себя и операторы сравнения (<, >, <=, >=, ==, !=). При вычислении они возвращают значение булева типа.
Условные выражения, в свою очередь, применяются в операторах ветвления (if-else, switch, тернарном). Подробнее об этих операторах тут.
Как применять
Допустим, мы хотим проверить, что значение переменной a больше значений в переменных b и c. То есть сравнить операнд a с двумя другими. Нам поможет логический оператор && (И).
Логический оператор && (И) возвращает true, если слева и справа от него стоят значения true, а иначе — false.
Иными словами, если оба логических высказывания истинны, то и операция && (И) возвращает истину.
Первый пример
Как вычисляется значение выражения (a > b) && (a > c):
Сначала проверяется условие (a > b). Оно вернёт true, так как 6 больше 4. Далее проверяется условие (a > c), которое также вернёт true, ведь 6 больше 3.
Теперь у нас с двух сторон от логического оператора && стоят значения true.
По определению выше или по таблице ещё выше, результат вычисления логического выражения (true && true) равен true.
Второй пример
Результат операции (a > b) вернёт true, так как 6 больше 4, а операция (a > c) уже вернёт false, так как 6 не больше 7.
Значит, слева от логического оператора && стоит true, а справа — false. Следовательно, результат вычисления логического выражения (мы присвоили его булевой переменной d) будет false.
Третий пример
Результат операции сравнения (a > b) равен false, а что вернёт операция (a > c), уже значения не имеет (смотрите определение выше) — результат вычисления логического выражения (мы присвоили его булевой переменной d) будет равен false.
Рассмотрим примеры с другими операторами.
OR
Порядок вычисления:
- (a > b) || (a > c)
- (4 > 6) || (4 > 3)
- false || (4 > 3)
- false || true
- true
Значение переменной d равно true.
Теперь вычисляйте вы.
XOR
Порядок вычисления:
- (b > a) ^ (c > a)
- (6 > 5) ^ (7 > 5)
- true ^ (7 > 5)
- true ^ true
- false
Значение d равно false.
NOT
Порядок вычисления:
- !(a > b)
- !(5 > 9)
- !false
- true
Значение d стало true.
Полные и сокращённые версии AND и OR
&& и || называются сокращёнными логическими операторами AND и OR соответственно, или операторами короткой схемы вычислений. В спецификации Java их ещё зовут условными. Значения их операндов могут быть только булева типа.
В отличие от двойных, одиночные & и | называются операторами полной схемы вычислений. Значения их операндов могут быть как только булевыми, так и только целочисленными (вместе с оператором ^ они используются в побитовых операциях).
В чём разница
В том, что для операторов & и | всегда вычисляются значения обоих операндов, а при работе операторов && и || второй операнд вычисляется только по необходимости.
То есть иногда результат выражения однозначно определён уже по первому операнду:
- Если первый операнд && равен false, то второй не вычисляется, так как уже понятно, что результат всего выражения будет false.
- Если первый операнд || равен true, то второй не вычисляется, так как уже понятно, что || вернёт true.
&& и || используют как операторы булевой логики. Они оперируют значениями только булева типа и применяются только в логических выражениях.
Как использовать
&& и || позволяют экономить вычисления (применять короткую схему) и помогают избегать ошибок. Как это делается?
Начнём с оператора &&. Приведём фрагмент из таблицы выше:
Логический оператор | Обозначение в Java | Выражение | Результат |
---|---|---|---|
«И» (AND): конъюнкция, логическое умножение | && | true && true false && false true && false false && true | true false false false |
Рассмотрим выражение: (3 > 4) AND (5 > 4)
Мы видим, что операнд слева от оператора AND равен false. Смотрим на таблицу выше — и понимаем, что вычислять второй операнд бессмысленно, так как оператор AND уже точно вернёт false.
Именно по такой логике и работает оператор короткой схемы вычислений &&. Если выражение слева от него равно false, то выражение справа вычисляться не будет.
Так же и с оператором ||: если выражение слева от него равно true, то выражение справа не вычисляется, так как результат операции || всё равно будет true.
В большинстве случае применяют именно && и ||. При верном использовании они избавляют Java от ненужных вычислений и страхуют от некоторых ошибок.
Первый пример
Если вместо оператора && мы используем &, то получим ошибку (исключение) java.lang.ArithmeticException: / by zero:
Ошибка возникнет тогда, когда Java попытается вычислить второй аргумент логического выражения, если первый равнялся false.
Иными словами, мы узнали, что b равно 0 (выражение b != 0 вернуло false) — и идём делить на b (делить на ноль), вычисляя значение второго операнда (a/b > 0).
Второй пример
Код выше выводит в консоль длину строки str, в которой есть хотя бы один символ. А если строка пуста или её значение равно null (то есть строковая переменная ни на что не указывает), в консоль выводится сообщение: «Тут нечего считать!»
Мы выбрали оператор короткой схемы вычислений && — и это правильно!
А вот если бы вместо этого использовали оператор полной схемы &, то наш код работал бы не так, как надо.
Мы получали бы ошибку NullPointerException каждый раз, когда вызываем метод для строковой переменной со значением null.
Посмотрим, что происходило бы при вычислении условия блока if:
- str != null & str.length() > 0
- null != null & str.length() > 0
- false & str.length() > 0 // тут возникает ошибка
Сперва вычисляется первый аргумент логического выражения, а именно str != null (иными словами, получаем ответ на вопрос «Строковая переменная не равна null?»). Получили false, значит всё же равна.
Дальше Java должна вычислить второй аргумент логического выражения, а именно str.length() > 0 (иными словами — проверяется «Число символов строки > 0?»).
Для этого вызывается метод str.length(), который должен вернуть целое значение. Оно и будет сравниваться с 0. Но у нас-то str равна null (возвращать методу нечего, строки нет). Тут Java и пожалуется на NullPointerException.
Порядок выполнения операторов
Когда в выражении несколько логических операторов, результат вычисляется с учётом их приоритета. Если нет логических скобок, то операции выполняются в таком порядке:
- ! (NOT)
- & (AND)
- ^ (XOR)
- | (OR)
- && (условный AND)
- || (условный OR)
Если одинаковые операции стоят по соседству, то раньше выполняется та, что левее.
Первый пример
Вычислим true ^ true & false:
- Выбираем самый приоритетный оператор (если таких больше одного — тот, что левее). У нас самый приоритетный & (он здесь такой один).
- Смотрим, что слева и справа от него: это true и false соответственно.
- Вычисляем выражение true & false — получаем false.
- В исходном выражении заменяем true & false результатом его вычисления (false) — и получаем: true ^ false.
- Вычислив это выражение, получаем результат true.
Или короче:
- true ^ true & false
- true ^ false
- true
Второй пример
Заменим & на &&:
Теперь самый приоритетный оператор в выражении это ^ — и порядок вычислений будет уже другой:
- true ^ true && false
- false && false
- false
Результат будет false.
Как изменить порядок вычисления
Порядок вычисления логических операторов меняют круглые скобки — так же, как в арифметике:
Добавив круглые скобки, мы поменяли приоритеты для вычисления. Теперь сперва будет определено выражение (true ^ true), которое вернёт false. А после — вычислится выражение false & false, которое тоже вернёт false.
То есть скобки повышают приоритет стоящего внутри выражения, а внутри самих скобок действуют прежние приоритеты.
Пример посложнее — выражение !(true && (false || true)) ^ !false.
Порядок вычисления:
- !(true && (false || true)) ^ !false
- !(true && true) ^ !false
- !true ^ !false
- false ^ !false
- false ^ true
- true
Результат: true.
Как логические операторы работают с целыми числами
Мы уже знаем, что логические операции применимы к логическим аргументам (операндам). Каждый логический операнд — это выражение, которое является истинным (true) или ложным (false) — то есть возвращает булево значение. Иными словами, логический операнд — это выражение типа boolean.
Выходит, применять логические операторы к целочисленным аргументам нельзя?
Можно. Внутри Java все целочисленные типы представлены двоичными числами разной длины. И к ним уже применимы бинарные логические операторы ^, | и &.
Только в этом случае они работают с двоичным представлением операндов — выполняют операции над их битами попарно (рассматривая их как логические единицы и нули). Поэтому и сами операторы ^, | и & зовутся побитовыми.
Как ^, | и & работают с целочисленными операндами
Рассмотрим пример:
Чтобы повторить вычисления Java, нужно:
- Перевести значения обоих операндов в двоичную систему счисления.
- Расположить результаты перевода друг под другом.
- Сравнять в них число разрядов (дополнить лидирующими нулями).
- Применить к битам из каждого столбца оператор (&, | или ^).
- Записать результат каждой операции ниже в том же столбце.
- Перевести итог в десятичную форму.
Потренируемся: вычислим сами 3 & 5
Число 3 в двоичной системе счисления имеет вид 11, а число 5 — 101.
Так как у числа 5 три разряда в двоичной системе, а у числа 3 — всего два, добавим лидирующий ноль к числу 3 в двоичной системе и получим 011.
Берём цифры из обоих чисел и применяем к ним попарно оператор & (AND):
3(10) = 011(2) | 0 | 1 | 1 |
& | & | & | |
5(10) = 101(2) | 1 | 0 | 1 |
= | = | = | |
001(2) = 1(10) | 0 | 0 | 1 |
Получаем число 001. В десятичной записи ему соответствует число 1. Поэтому операция 3 & 5 и возвращает в результате 1.
Вычислим 3 | 5
С оператором | действуем так же:
3(10) = 011(2) | 0 | 1 | 1 |
| | | | | | |
5(10) = 101(2) | 1 | 0 | 1 |
= | = | = | |
111(2) = 7(10) | 1 | 1 | 1 |
Рассчитаем 3 ^ 5
3(10) = 011(2) | 0 | 1 | 1 |
^ | ^ | ^ | |
5(10) = 101(2) | 1 | 0 | 1 |
= | = | = | |
110(2) = 6(10) | 1 | 1 | 0 |
Что дальше?
Сперва подытожим:
- мы познакомились с логическими операторами в Java;
- научились вычислять условные выражения с ними;
- разобрались, как они работают с целыми числами.
В одной из статей мы говорили про операторы сравнения <, >, <=, >=, ==, !=, а также instanceof, про условные конструкции if-else и switch. Учились работать с тернарным оператором.
Если пропустили — лучше вернитесь и прочтите.