User Эллеонора Керри
Эллеонора Керри
41 уровень

Логические операции в Java. Поразрядные операции в Java

Статья из группы Java Developer
Логические операции в Java. Поразрядные операции в Java - 1

Логические операции в Java

Логические операции выполняются с помощью логических операторов. Уж простите за тавтологию, но дела обстоят именно так. Основные логические операции (в программировании и математике) можно применять к логическим аргументам (операндам), а также составлять более сложные выражения, подобно арифметическим действиям над числами. Например выражение:

(a | b) | (c < 100) & !(true) ^ (q == 5)
представляет собой сложное логическое выражение с четырьмя операндами: (a | b), где а и b — переменные типа boolean (c < 100) (true) (q == 5) В свою очередь, простое логическое выражение (a | b) также состоит из двух аргументов-операндов. Логический операнд — это выражение, о котором можно сказать что оно является истинным или ложным, true или false. Говоря языком Java, логический операнд — это выражение типа boolean или Boolean, например:
  • (2 < 1) — логический операнд, его значение равно false
  • true — логический операнд, значение которого, очевидно, true
  • boolean a — тоже может быть логическим операндом, как и Boolean a
  • int a = 2не является логическим операндом, это просто переменная типа int
  • String a = "true" также не является логическим операндом. Это строка, текстовое значение которой — "true".
В Java доступны следующие логические операции:
  • Логическое отрицание, оно же NOT или инверсия. В Java обозначается символом “!” перед операндом. Применяется к одному операнду.
  • Логическое и, оно же AND или конъюнкция. Обозначается символом “&” между двумя операндами, к которым применяется.
  • Логическое или в Java, оно же — OR, оно же — дизъюнкция. В Java обозначается символом “|” между двумя операндами.
  • Исключающее или, XOR, строгая дизъюнкция. В Java обозначается символом “^” между двумя операндами.
  • В Java к логическим операторам можно отнести условное или, обозначаемое как ||, а также условное и&&.
Примечание: также в математической логике рассматривают отношение эквивалентности, проще говоря — равенства. Однако в Java оператор равенства == не принято относить к логическим. Внимание! В Java логические операторы &, | и ^ применяются также к целым числам. В этом случае они работают несколько иначе и называются поразрядными (или побитовыми) логическими операторами. О них — ближе к концу статьи. Рассмотрим таблицу с кратким описанием каждого из логических операторов Java, а ниже опишем их подробнее и приведем примеры кода.
Оператор Java Имя Тип Краткое описание Пример
! Логическое “не” (отрицание) Унарный !x означает “не x”. Возвращает true если операнд является false. Возвращает false если операнд является true. boolean x = true;
Тогда
// !x == false
& Логическое И (AND, умножение) Бинарный Возвращает true если оба операнда равны true. a = true;
b = false;
тогда
a & b == false
| Логическое ИЛИ (OR, сложение) Бинарный Возвращает true если хотя бы один из операндов равен true. a = true;
b = false;
тогда
a | b == true
^ Логическое исключающее ИЛИ (XOR) Бинарный Возвращает true, если один и только один из операндов равен true. Возвращает false, если оба операнда равны true или false. По сути, возвращает true, если операнды — разные. a = true;
b = false;
тогда
a ^ b == true
&& Условное И (сокращённое логическое И) Бинарный То же самое, что и &, но если операнд, находящийся слева от & является false, данный оператор возвращает false без проверки второго операнда.
|| Условное ИЛИ (сокращённое логическое ИЛИ) Бинарный То же самое, что и |, но если оператор слева является true, оператор возвращает true без проверки второго операнда.

Логические операции в курсе JavaRush

Без логических операций никуда не деться, и в курсе JavaRush они появляются с первых уровней, вместе с условиями и типом данных boolean. Пользоваться методами математической логики программисты приучаются постепенно. Для более уверенных манипуляций с логическими конструкциями требуется определённая сноровка и понимание некоторых процессов. Так что подробнее и уже на совсем другом уровне к этим операциям подходят в конце квеста Multithreading, когда большинство студентов уже не слишком отвлекается непосредственно на синтаксис и конструкции, а старается вникать в суть задачи.

Логические операции в Java. Поразрядные операции в Java - 2

Оператор логического отрицания !

Этот оператор — унарный, то есть он применяется к одному булевскому выражению или операнду. Понять его очень просто, как и любое отрицание: оператор просто меняет значение выражения на противоположное. Таблица истинности или результаты выполнения операции отрицания:
Значение a !a
false true
true false
Пример. Операция логического отрицания

public class Solution {
   public static void main(String[] args) {
       boolean a = true;
       System.out.println(!a); // здесь наше логическое выражение меняет значение на противоположное
       System.out.println(!false); // выражение не-false, как можно догадаться, будет равно... чему?
       System.out.println(!(2 < 5)); // выражение (2 < 5) истинно, значит, его отрицание - ложно 

   }
}
Вывод программы будет следующий:

false
true
false

Логическое И — &, а также условное И — &&

Логическое И или конъюнкцию применяют к двум выражениям, и результат его действия будет истинным (true) только если оба операнда истинны. То есть, если один из операндов a или b равен false, то выражение a & b будет false независимо от значения второго оператора. Если представить, что true — это число 1, а false — 0, то оператор & работает точно так же, как обычное умножение. Поэтому логическое И часто называют “логическим умножением”. И, кстати, этот факт помогает быстрее запомнить работу оператора & и не путать его с оператором логического или |. Таблица истинности И, она же — результат работы оператора &
a b a & b
true true true
true false false
false true false
false false false
Логическое И, оно же — конъюнкция, примеры:

public class Solution {
   public static void main(String[] args) {
       boolean a = true;
       boolean b = false;
       boolean c = true;
       System.out.println(a & b); // если мы умножим правду на ложь, то определённо получим ложь 
       System.out.println(a & c); // правда на правду будет правда 
       System.out.println(false & (2 > 5));
 System.out.println((2 < 5) & false);
 // независимо от правдивости выражения в скобках, в таком случае нам приходится довольствоваться ложью 
   }
}
Результат работы программы:

false
true
false
false
Оператор && иногда называют “сокращённым И”. Он выдаёт такой же результат при работе с логическими операндами, что и оператор &. Однако есть разница в самой его работе. Так, вы уже успели заметить, что если в выражении (a & b) операнд a равен false, то не имеет смысла проверять значение операнда b: результат операции точно будет false. Так что если нам принципиально не нужно значение второго операнда, с помощью && мы сокращаем количество вычислений в программе. Если мы заменим в примере все операторы & на &&, результат работы будет точно таким же, но сама программа будет работать чуточку быстрее (правда, мы этого не заметим, так как речь идёт о мили-микро… короче говоря, об очень маленьких единицах времени).

Логическое ИЛИ — оператор |, а также условное ИЛИ — оператор ||

Оператор ИЛИ в Java обозначается символом |. Логическое ИЛИ или дизъюнкцию применяют к двум выражениям, и результат его действия будет ложным (false) тогда и только тогда, когда оба операнда ложны. Здесь мы в какой-то мере наблюдаем ту же картину, что и в случае с оператором &, но с точностью до наоборот. То есть, если хотя бы один операнд равен true, то выражение a | b гарантированно будет true независимо от значения второго оператора. Если & ведёт себя как логическое умножение, то ИЛИ — это логическое сложение, если представить, что true — это 1, а false — 0. Только следует помнить, что логическое сложение работает не так, как обычное. 1 + 1 в данном случае равно не 2, а 1 (числа 2 в этой системе просто не существует). Иногда дизъюнкцию понимают как максимум из 0 и 1, и в таком случае если хотя бы один операнд равен 1 (true), мы получим именно true. Таблица истинности ИЛИ, она же — результат работы оператора |:
a b a | b
true true true
true false true
false true true
false false false
Логическое ИЛИ, оно же — дизъюнкция, пример:

public class Solution {
   public static void main(String[] args) {
       boolean a = true;
       boolean b = false;
       boolean c = true;
       System.out.println(!a | b); // Скомпонуем использование двух логических операторов: a == true, значит, !a, как мы уже знаем - это false.
       System.out.println(a | c); 
       System.out.println((2 < 5) | false); // выражение (2 < 5) истинно, значит, при любом втором операнде мы получим получим истинный результат
       System.out.println((2 > 5) | true);

   }
}
Результат:

false
true
true
true
Если мы применим оператор условного ИЛИ — || вместо |, мы получим ровно тот же результат, но, как и в случае с условным И &&, он будет действовать экономно: если мы “нарываемся” на первый операнд равный true, значение второго операнда не проверяется, а сразу выдаётся результат true.

XOR Java — логическое исключающее ИЛИ — оператор ^

XOR, сложение по модулю 2, логическое исключающее ИЛИ, логическое вычитание, строгая дизъюнкция, поразрядное дополнение… у оператора ^ есть много имён в булевой алгебре. Результат применения этого оператора к двум операндам будет равен true, если операнды разные и false, если операнды одинаковые. Поэтому его удобно сравнивать с вычитанием нулей (false) и единиц (true). Таблица истинности XOR, она же — результат работы оператора ^:
Boolean a Boolean b a ^ b
true true false
true false true
false true true
false false false
Пример:

public class Solution {
   public static void main(String[] args) {
       boolean a = true;
       boolean b = false;
       boolean c = true;
       System.out.println(!a ^ b); // Скомпонуем использование двух логических операторов: a == true, значит, !a, как мы уже знаем - это false.
       System.out.println(a ^ c);
       System.out.println((2 < 5) ^ false);
       System.out.println((2 > 5) ^ true);
   }
}
Результат:

false
false
true
true

Приоритет логических операций

Как и в математике, в программировании у операторов есть определённый порядок выполнения, если они встречаются в одном выражении. Унарные операторы имеют преимущества над бинарными, а умножение (даже логическое) над сложением. Мы расположили логические операторы в списке тем выше, чем выше их приоритет:
  1. !
  2. &
  3. ^
  4. |
  5. &&
  6. ||
Рассмотрим примеры. Конъюнкция и дизъюнкция (& и |) имеют разный приоритет:

public class Solution {
   public static void main(String[] args) {      
       boolean a = true, b = true, c = false;
       System.out.println(a | b & c);   
}
Если бы мы действовали слева направо, то есть сначала применили бы оператор |, а затем — &, мы бы получили значение false. Но на деле, если вы запустите данную программу на выполнение, то убедитесь, что вывод будет true, поскольку у оператора логического И & приоритет будет выше, нежели у оператора логического ИЛИ |. Чтобы не путаться, нужно помнить, что & ведёт себя как умножение, а | — как сложение. Поменять порядок приоритета, можно. Просто примените скобки, прямо как в школьной математике. Изменим немного код нашего примера:

public class Solution {
   public static void main(String[] args) {      
       boolean a = true, b = true, c = false;
       System.out.println((a|b)&c);   
}
Что тут? Сначала применяем логическое сложение в скобках, а затем уже умножение. Результатом будет false.

Сложные логические выражения

Разумеется, мы можем комбинировать логические выражения и операторы. Вспомним выражение из начала статьи:

(a | b) | (c < 100) & !(true) ^ (q == 5)
Теперь оно выглядит не так страшно. Напишем программу, которая выводит его значение, предварительно определив значения a, b, с и q. Пример вычисления значения сложного логического выражения

public class Solution {
   public static void main(String[] args) {
       boolean a = true;
       boolean b = false;
       int c = 25;
       int q = 2;
       System.out.println((a|b) | (c < 100) & !(true)^(q == 5)); 
   }
}
Обратите внимание: переменная q у нас относится к типу int, а вот q == 5 — это булево выражение, и оно равно false, поскольку выше мы проинициализировали q числом 2. То же самое и с переменной c. Это число равное 25, а вот (c < 100) — булево выражение, равное true. Результат работы этой программы:

true
Сложные логические выражения можно применять для проверки очень запутанных и ветвистых условий, однако не стоит ими злоупотреблять: они затрудняют чтение кода.

Поразрядные (побитовые) операторы

В начале статьи мы упомянули, что операторы &, | и ^ можно употреблять по отношению к целочисленным типам Java. В таком случае они являются поразрядными операторами. Их также называют побитовыми, поскольку один разряд — это и есть один бит, а эти операции работают именно с битами. Разумеется, работают они несколько иначе, нежели логические операторы, и чтобы понимать, как именно, нужно знать, что такое двоичная (бинарная) система счисления. Если вы ничего о ней не знаете или совсем забыли, предлагаем для начала ознакомиться со статьёй Java: биты и байты, а всем остальным напомним, что в двоичной системе счисления есть всего две цифры — 0 и 1, и все данные в компьютере представлены именно с помощью условных нуликов и единичек. Любое из привычных нам чисел (десятичных; для них есть 10 разных цифр от 0 до 9, с помощью которых мы записываем любые числа) представимо в двоичной системе счисления. Перевести десятичное число в двоичное можно с помощью последовательного деления в столбик на основу системы счисления (2). Остатки от деления на каждом шагу, записанные в обратном порядке, и дадут нам искомое двоичное число. Вот, например, перевод десятичного числа 103 в двоичное представление: Логические операции в Java. Поразрядные операции в Java - 3

Двоичная система счисления в курсе JavaRush

В курсе JavaRush о двоичной системе счисления рассказывают во время изучения квеста MultiThreading (10 уровень, 1 лекция), после лекции есть несколько задач на закрепление. Однако эта тема совсем не сложная, и даже если вы ещё не прошли по курсу так далеко, скорее всего, вы в ней разберётесь.

Помимо &, | и ^ в Java также используются поразрядные операторы:
  • ~ поразрядный оператор отрицания
  • >> побитовый сдвиг вправо
  • >>> беззнаковый побитовый сдвиг вправо
  • << побитовый сдвиг влево
Новичкам поразрядные операторы кажутся очень непонятными и искусственными. Им чаще всего непонятно, для чего они нужны, кроме как для решения учебных задачек. На самом деле, их можно применять как минимум для организации эффективного деления и умножения, а профессионалы их используют для кодирования/декодирования, шифрования, генерации случайных чисел.

Поразрядные операторы &, | и ^

Давайте рассмотрим на примере, как работают эти операторы. Допустим у нас есть два целых числа:

int a = 25; 
int b = 112; 
Нам нужно применить к ним три операции &, | и ^ и вывести на экран результат. Вот код программы:

public class Solution {
   public static void main(String[] args) {

       int a = 25;
       int b = 112;

       int res1 = a & b;
       int res2 = a | b;
       int res3 = a ^ b;

       System.out.println("a & b = " + res1);
       System.out.println("a | b = " + res2);
       System.out.println("a ^ b = " + res3);
      
   }
}
Результат работы программы следующий:

a & b = 16
a | b = 121
a ^ b = 105
Если не понимать, что происходит, то результат выглядит весьма и весьма загадочным. На самом деле, всё проще, чем кажется. Поразрядные операторы “видят” числа-операнды в их двоичной форме. И затем применяют логические операторы &, | или ^ к соответствующим друг другу разрядам (битам) обоих чисел. Так, для & последний бит двоичного представления числа 25 логически складывается с последним битом двоичного представления числа 112, предпоследний — с предпоследним, и так далее: Логические операции в Java. Поразрядные операции в Java - 4Та же логика прослеживается в случае с | и ^. Логические операции в Java. Поразрядные операции в Java - 5

Побитовый сдвиг влево или вправо

В Java существует несколько операторов побитового сдвига. Чаще всего используют операторы << и >>. Они сдвигают двоичное представление числа соответственно влево или вправо, причём в случае сдвига вправо — с сохранением знака (что значит сохранение знака, расскажем чуть ниже). Есть ещё один оператор сдвига вправо >>>. Он делает то же самое, что и >> но знак не сохраняет. Итак, рассмотрим их работу на примере. int a = 13 a << 1 смещает все биты двоичного представления числа a влево на 1 бит. Для упрощения представим число 13 в двоичном виде как 0000 1101. На самом деле это число выглядит так: 00000000 00000000 00000000 00001101, поскольку под числа типа int Java выделяет 4 байта или 32 бита. Однако в примере это роли не играет, так что в этом примере будем мнить наше число однобайтовым. Логические операции в Java. Поразрядные операции в Java - 6Освободившийся справа бит заполняется нулями. В результате такой операции мы получим число 26. a << 2 смещает все биты двоичного представления числа a влево на 2 бита, и освободившиеся справа два бита заполняются нулями. В результате мы получим число 52. a << 3 выдаст результат 104… Заметили закономерность? Побитовый сдвиг a влево на n позиций работает как умножение числа a на 2 в степени n. Это же касается и отрицательных чисел. Так -13 << 3 выдаст результат -104. a >> n смещает двоичное представление число на n позиций вправо. Например, 13 >> 1 Превращает число 1101 в число 0110, то есть, 6. А 13 >> 2 даст в результате 3. То есть по сути, тут мы делим число на 2 в степени n, где n — количество сдвигов вправо, но с одним нюансом: если число нечётное, мы при этой операции как бы обнуляем последний бит числа. А вот с отрицательными дело обстоит несколько иначе. Скажем, попробуйте проверить, что выдаст программа, если вы попросите её выполнить операцию -13 >> 1. Вы увидите число -7, а не -6, как можно было бы подумать. Так происходит из-за особенностей хранения отрицательных чисел в Java и других языках программирования. Они хранятся в так называемом дополнительном коде. При этом старший разряд (тот, что слева) отдаётся под знак. В случае с отрицательным числом старший разряд равен 1.

Дополнительный код

Рассмотрим число int a = 13. Если в программе вы выведем его двоичное представление в консоль помощью команды System.out.println(Integer.toBinaryString(a));, то мы получим 1101. На самом деле это — сокращённая запись, поскольку число типа int занимает в памяти 4 байта, поэтому компьютер “видит” его, скорее так:

00000000 00000000 00000000 00001101
Старший разряд равен нулю, значит, перед нами положительное число. Для перевода в дополнительный код:
  1. Записываем число -13 в так называемом “прямом коде”. Для этого меняем старший разряд числа на 1.
    Результат действия:

    
    10000000 0000000 0000000 00001101
    
  2. Далее инвертируем все разряды (меняем 0 на 1, а 1 на 0) кроме знакового разряда. Его, по сути, мы уже поменяли.
    Результат действия:

    
    11111111 11111111 11111111 11110010
    

    (да, шаги 1 и 2 можно было бы совместить, но лучше представлять именно так)

  3. Прибавляем к получившемуся числу 1.
    Результат действия:

    
    11111111 11111111 11111111 11110011
    
Получившееся двоичное число — это и есть -13, записанное в дополнительном коде и побитовый сдвиг (да и другие операции) будут применяться именно к нему. Просто разница в логике работы заметна далеко не на всех операциях. Скажем, для того же сдвига влево разница незаметна, мы можем работать оперируя отрицательными числами так же, как и положительными числами. Теперь выполним сдвиг вправо -13 >> 1. Поскольку наш оператор >> сохраняет знак, то в этой операции все освободившиеся слева биты заполняются не нулями, а единицами. Таким образом сдвигая число

11111111 11111111 11111111 11110011
на один бит вправо, в результате мы получим следующую последовательность бит:

11111111 11111111 11111111 11111001
Если перевести это число в прямой код (то есть сначала отнять 1, затем инвертировать все биты, кроме первого) мы получим число:

10000000 00000000 00000000 00000111
или -7. Теперь, когда мы разобрались с оператором сдвига вправо с сохранением знака, станет понятно, в чем его отличие от оператора >>>. a >>> n — эта операция является беззнаковым сдвигом, то есть она сдвигает двоичное представление числа a вправо на n разрядов, но освободившиеся слева n разрядов заполняет не единицами, как оператор >>, а нулями. Проделаем операцию -13 >>> 1. У нас уже есть число -13 в дополнительном коде:

11111111 11111111 11111111 11110011
При сдвиге вправо на 1 бит и заполнении освободившийся бит нулём мы получаем следующее число:

01111111 11111111 11111111 11111001
Что в десятичном представлении даёт число 2147483641.

Побитовый оператор отрицания ~

Этот унарный оператор работает очень просто: он меняет каждый бит бинарного представления целого числа на противоположный. Возьмем число -13:

11111111 11111111 11111111 11110011
Операция побитового отрицания ~13 просто изменит значение каждого бита на противоположное. В результате мы получим:

00000000 00000000 00000000 00001100
Или 12 в десятичном виде.

Краткие выводы

  • Все логические операторы применяются к булевским выражениям, то есть таким, о которых можно сказать, true они или false.
  • Если операторы &, | или ^ применяются к числам, речь идёт уже не о логических операциях, а о побитовых. То есть оба числа переводятся в двоичную систему и к этим числам побитово применяют операции логического сложения, умножения или вычитания.
  • В математической логике операторам & и | соответствуют конъюнкция и дизъюнкция.
  • Логическое И похоже на умножения 1 (true) и 0 (false).
  • Логическое ИЛИ напоминает поиск максимума среди 1 (true) и 0 (false).
  • Для побитового отрицания целого числа a используется операция ~a.
  • Для логического отрицания булевского выражения a используется операция !a.
  • Отрицательные числа хранятся и обрабатываются в дополнительном коде.
  • Поразрядный сдвиг вправо может сохранять знак (>>), а может — не сохранять (>>>).
Комментарии (14)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Morgoth Hel Уровень 8, Москва
15 ноября 2020
Отличная статья, рекомендовал бы любому время от времени перечитывать)
Vanya Уровень 1
1 июня 2020
Может кто объяснить, зачем побитовое мне щас? достаточно знать &&, || и все же? да и вообще в будущем все это побитовое лучше же исключить, чтоб код был читаемый без всего этого.
Серго Уровень 12, видное, Абхазия
12 января 2020
может мне кто нибудь обьяснить, как печатать с клавы эти символы ||||||, я уже горю, даже в гугле найти не могу:DDDD А копипастить из лекций надоело
Ivan Vdovin Уровень 9, Липецк , Россия
4 января 2020
Приятно удивлён, что АЖ на 10 уровне будут темы из уроков информатики, 6 класс
Миша Небоярски Уровень 16, Прага, Чехия
12 сентября 2019
Если мы все равно получим от || такой же результат как от |, и от && такой же результат, как и от &, и работают они еще и не медленнее, то можете привести пример, когда мы захотим воспользоваться именно | или &?