JavaRush /Java блог /Random UA /Логічні оператори в Java

Логічні оператори в Java

Стаття з групи Random UA
Логічні операції у 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, оскільки під числа типуintJava виділяє 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.
  • Негативні числа зберігаються та обробляються у додатковому коді.
  • Порозрядний зсув праворуч може зберігати знак ( >>), а може – не зберігати ( >>>).
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ