Давай поговорим об операциях в Java: числовых, логических, побитовых. Это теоретический базис, который однозначно нужен, чтобы научиться программировать. Если коротко: оператор это знак операции (
+,
&&,
>>), а операнд это то, над чем она выполняется. В Java операторы делятся на четыре группы: арифметические работают с числами, операторы сравнения дают
true или
false, логические работают с
boolean, а побитовые с отдельными битами целого числа. Ниже разберем все четыре группы с примерами.
Кратко
- Оператор это знак операции, операнд это то, над чем она выполняется. Унарный оператор работает с одним операндом, бинарный с двумя.
- Арифметические операторы:
+, -, *, / и % (остаток от деления), плюс унарные +, -, ++, --. Любой из них совмещается с присваиванием: x += 10.
++ и -- ведут себя по-разному в двух формах: в x = a++ в выражение попадет старое значение a, в x = ++a уже увеличенное.
- Операторы сравнения
==, !=, >, >=, <, <= всегда возвращают true или false.
- Логические
!, &&, ||, &, |, ^ работают с boolean. Сокращенные && и || не вычисляют второй операнд, если ответ ясен уже по первому.
- Побитовые
~, &, |, ^, <<, >>, >>> работают с целыми числами по каждому биту. Сдвиг влево на n умножает число на 2 в степени n, сдвиг вправо делит.
Какие бывают операторы в Java
Для любой операции нам нужно как минимум две вещи:
![Коллаж из цветных фигур с символами операторов Java: XOR, амперсанды, знаки равенства и неравенства, стрелки сдвига, слово byte и ряд нулей с единицами]()
Примером оператора может быть простой плюс в операции сложения двух чисел. А складываемые друг с другом числа будут в этом случае операндами. Итак, с помощью операторов мы выполняем операции над одним или несколькими операндами.
Операторы, которые осуществляют операции над двумя операндами, называются бинарными. Например, сложение двух чисел.
Операторы, которые осуществляют операции над одним операндом, называются унарными. Например, унарный минус.
Операторы Java в курсе JavaRush
Несколько лекций посвящено операторам Java на
четвертом уровне первого квеста Java Syntax. В частности,
условным операторам, типу boolean. В курсе есть 22 задачи, которые помогут разобраться с работой операторов сравнения, условных, логических операторов.
Операции над числами в Java
Самая частая операция, которую программисты производят над числами, это присвоение числового значения какой-либо переменной. Она, как и оператор
= тебе знакомы:
int a = 1;
int b = 2;
int c = 3;
Есть также арифметические операции. Они осуществляются с помощью бинарных арифметических операторов:
Таблица 1. Бинарные арифметические операторы
| Оператор |
Описание |
+ |
Оператор сложения: складывает два числа |
- |
Оператор вычитания: вычитает одно число из другого |
* |
Оператор умножения: умножает одно число на другое |
/ |
Оператор деления: делит одно число на другое. |
% |
Оператор остатка от деления: вычисляет остаток от деления одного числа на другое |
Первые четыре оператора не должны вызывать вопросов: все так же, как в математике. Последний оператор, остаток от деления, также не делает ничего сверхсложного. К примеру, если разделить 24 на 7, мы получим 3 целых и 3 в остатке. Именно остаток и вернет данный оператор:
System.out.println(24 % 7); // выведет 3
И еще одна вещь, о которую спотыкаются все новички: деление двух целых чисел в Java тоже дает целое, дробная часть просто отбрасывается.
System.out.println(7 / 2); // выведет 3, а не 3.5
System.out.println(7 / 2.0); // выведет 3.5
Чтобы получить дробный результат, хотя бы один операнд должен быть вещественным, например
double.
Вот примеры с сайта
официальной документации Oracle:
![Листинг класса ArithmeticDemo из документации Oracle: к переменной result по очереди применяются сложение, вычитание, умножение, деление и остаток от деления]()
Данная программа выведет следующее:
1 + 2 = 3
3 - 1 = 2
2 * 2 = 4
4 / 2 = 2
2 + 8 = 10
10 % 7 = 3
Java позволяет комбинировать: например, операторы присваивания и арифметические операторы.
Рассмотрим пример:
int x = 0;
x = x + 1; // x = 0 + 1 => x = 1
x = x + 1; // x = 1 + 1 => x = 2
x = x + 1; // x = 2 + 1 => x = 3
Здесь мы задали переменную
x и присвоили ей нулевое значение. Далее в каждой строке мы присваиваем значению
x сумму текущего значения переменной
x и единицы. В комментариях к каждой строке есть пояснения. Эту процедуру называют наращиванием или инкрементированием переменной.
Операцию инкрементирования из примера выше можно заменить на аналогичную с использованием комбинации операторов:
int x = 0;
x += 1; // x = 0 + 1 => x = 1
x += 1; // x = 1 + 1 => x = 2
x += 1; // x = 2 + 1 => x = 3
Комбинировать оператор присваивания можно с любым арифметическим оператором:
int x = 0;
x += 10; // x = 0 + 10 => x = 10
x -= 5; // x = 10 - 5 => x = 5
x *= 5; // x = 5 * 5 => x = 25
x /= 5; // x = 25 / 5 => x = 5
x %= 3; // x = 5 % 3 => x = 2;
Продемонстрируем работу последнего примера:
![IntelliJ IDEA: класс ArithmeticDemo с комбинированными операторами, в консоли выводится x = 10, x = 5, x = 25, x = 5, x = 2]()
Помимо бинарных, в Java есть унарные арифметические операторы.
Таблица 2. Унарные арифметические операторы:
| Оператор |
Описание |
+ |
Унарный плюс: числа являются положительными по умолчанию. |
- |
Унарный минус: меняет знак числа на противоположный |
++ |
Оператор инкремента: увеличивает переменную на 1 |
-- |
Оператор декремента: уменьшает переменную на 1 |
Пример унарных плюса и минуса:
int x = 0;
x = (+5) + (+15); //Скобки для наглядности, можно и без них
System.out.println("x = " + x);
int y = -x;
System.out.println("y = " + y);
![IntelliJ IDEA: пример с унарными плюсом и минусом, в консоли выводится x = 20 и y = -20]()
Операции инкремента и декремента по сути просты. В первом случае происходит увеличение переменной на 1, во втором уменьшение переменной на 1. Пример ниже:
int x = 9;
x++;
System.out.println(x); // 10
int y = 21;
y--;
System.out.println(y); // 20
Есть два типа данных операций: постфиксная и префиксная. В первом случае оператор пишется после переменной, во втором случае перед переменной. Разница лишь в том, когда выполнится операция инкрементирования или декрементирования. Пример и описание в таблице ниже.
Предположим, у нас есть переменная:
int a = 2;
Тогда:
Таблица 3. Операторы инкремента-декремента:
| Форма |
Выражение |
x |
Пояснение |
Постфикс, ++ |
int x = a++; |
2 |
a сначала участвует в выражении, затем наращивается |
Префикс, ++ |
int x = ++a; |
3 |
a сначала наращивается, затем участвует в выражении |
Постфикс, -- |
int x = a--; |
2 |
a сначала участвует в выражении, затем уменьшается |
Префикс, -- |
int x = --a; |
1 |
a сначала уменьшается, затем участвует в выражении |
Демонстрация:
![IntelliJ IDEA: префиксный декремент при a равном 2, в консоли выводится x = 1 и a = 1]()
Помимо арифметических, существуют операции сравнения (двух чисел). Результатом всегда будет истина либо ложь (
true /
false).
Таблица 4. Операторы сравнения
| Оператор |
Описание |
== |
Определяет, что два числа равны друг другу |
!= |
Определяет, что два числа друг другу не равны |
> |
Определяет, больше ли одно число другого |
>= |
Определяет, что одно число больше либо равно другого числа |
< |
Определяет, меньше ли одно число другого |
<= |
Определяет, что одно число меньше либо равно другого числа |
Примеры:
int a = 1;
int b = 2;
boolean comparisonResult = a == b;
System.out.println("a == b :" + comparisonResult);
comparisonResult = a != b;
System.out.println("a != b :" + comparisonResult);
comparisonResult = a > b;
System.out.println("a > b :" + comparisonResult);
comparisonResult = a >= b;
System.out.println("a >= b :" + comparisonResult);
comparisonResult = a < b;
System.out.println("a < b :" + comparisonResult);
comparisonResult = a <= b;
System.out.println("a <= b :" + comparisonResult);
Демонстрация:
![IntelliJ IDEA: класс ComparisonDemo сравнивает a равное 1 и b равное 2, в консоли false, true, false, false, true, true]()
Логические операции в Java
Рассмотрим логические операции и таблицы истинности каждой из них:
- операция отрицания (
NOT);
- операция конъюнкции, логическое И (
AND);
- операция дизъюнкции, логическое ИЛИ (
OR);
- операция сложения по модулю, исключающее ИЛИ (
XOR).
Операция отрицания унарная и применяется к одному операнду. Все остальные операции бинарные.
Рассмотрим таблицы истинности данных операций. Здесь
0 это аналог значения
false в Java, а
1 это аналог
true.
Таблица 5. Таблица истинности оператора отрицания (NOT)
Таблица 6. Таблица истинности оператора конъюнкции (AND)
| a |
b |
a AND b |
| 0 |
0 |
0 |
| 0 |
1 |
0 |
| 1 |
0 |
0 |
| 1 |
1 |
1 |
Таблица 7. Таблица истинности оператора дизъюнкции (OR)
| a |
b |
a OR b |
| 0 |
0 |
0 |
| 0 |
1 |
1 |
| 1 |
0 |
1 |
| 1 |
1 |
1 |
Таблица 8. Таблица истинности оператора сложения по модулю (XOR)
| a |
b |
a XOR b |
| 0 |
0 |
0 |
| 0 |
1 |
1 |
| 1 |
0 |
1 |
| 1 |
1 |
0 |
В Java есть те же логические операции:
! это оператор отрицания;
&& это оператор логическое И (сокращенный);
|| это оператор логическое ИЛИ (сокращенный);
& это логическое И (несокращенное), оно же побитовое И для целых чисел;
| это логическое ИЛИ (несокращенное), оно же побитовое ИЛИ для целых чисел;
^ это исключающее ИЛИ, оно же побитовое исключающее ИЛИ для целых чисел.
Обрати внимание: у
&,
| и
^ две роли. С операндами типа
boolean это логические операторы, и результат тоже
boolean. С целыми числами это побитовые операторы, и результат целое число. Смешивать типы нельзя: оба операнда должны быть либо булевыми, либо целыми. Каждый из них подробнее разобран в статье про
логические операторы в Java. Разницу между побитовыми и сокращенными операторами рассмотрим чуть ниже, пока давай преобразуем все таблицы истинности в Java код:
public class LogicDemo {
public static void main(String[] args) {
notExample();
andExample();
orExample();
xorExample();
}
public static void notExample() {
System.out.println("NOT EXAMPLE:");
System.out.println("NOT false = " + !false);
System.out.println("NOT true = " + !true);
System.out.println();
}
public static void andExample() {
System.out.println("AND EXAMPLE:");
System.out.println("false AND false = " + (false & false));
System.out.println("false AND true = " + (false & true));
System.out.println("true AND false = " + (true & false));
System.out.println("true AND true = " + (true & true));
System.out.println();
}
public static void orExample() {
System.out.println("OR EXAMPLE:");
System.out.println("false OR false = " + (false | false));
System.out.println("false OR true = " + (false | true));
System.out.println("true OR false = " + (true | false));
System.out.println("true OR true = " + (true | true));
System.out.println();
}
public static void xorExample() {
System.out.println("XOR EXAMPLE:");
System.out.println("false XOR false = " + (false ^ false));
System.out.println("false XOR true = " + (false ^ true));
System.out.println("true XOR false = " + (true ^ false));
System.out.println("true XOR true = " + (true ^ true));
System.out.println();
}
}
Данная программа выведет на экран:
NOT EXAMPLE:
NOT false = true
NOT true = false
AND EXAMPLE:
false AND false = false
false AND true = false
true AND false = false
true AND true = true
OR EXAMPLE:
false OR false = false
false OR true = true
true OR false = true
true OR true = true
XOR EXAMPLE:
false XOR false = false
false XOR true = true
true XOR false = true
true XOR true = false
Логические операторы применимы только к
boolean переменным. В нашем случае мы применяли их сразу к значениям, но можно их использовать и с
boolean переменными:
![IntelliJ IDEA: класс LogicDemo с условием из двух булевых переменных, в консоли Something happened]()
И к
boolean выражениям:
![IntelliJ IDEA: условие о том, что возраст не меньше 20 и не больше 45, при age равном 33 в консоли Age is between 20 and 45]()
Теперь, у нас есть сокращенные операторы (
&&,
||) и аналогичные побитовые операторы (
&,
|). В чем между ними разница?
Во-первых, побитовые можно применять к целым числам. Об этом мы поговорим чуть позже.
А во-вторых, одни сокращенные, а другие нет. Чтобы понять, как выглядит сокращенность, посмотрим на выражение:
false AND x = ?
true OR x = ?
Здесь
x может принимать любое булево значение. И в целом, согласно законам логики и таблицам истинности, независимо от того, будет
x true или
false, результатом первого выражения будет
false, а результатом второго будет
true. Смотри.
![IntelliJ IDEA: вывод результатов AND и OR для пар булевых значений, в консоли false, false, затем true, true]()
Иногда результат выражения можно вычислить уже по первому операнду. Этим и отличаются сокращенные операторы
&& и
||. В выражениях, аналогичных описанным выше, они не вычисляют значение второго операнда. Вот небольшой пример:
![IntelliJ IDEA: сокращенное ИЛИ из двух вызовов методов, в консоли только Getting true value, второй метод не вызывается]()
В случае с сокращенными операторами не вычисляется вторая часть выражения. Но происходит это только тогда, когда результат выражения очевиден уже по первому операнду.
Побитовые операции в Java
Ну вот мы и подобрались к самому интересному: побитовым операциям. Как можно понять из названия, это операции, которые производятся над битами. Но прежде чем мы погрузимся в эту тему, стоит поговорить о смежных областях.
Представление чисел в двоичной системе счисления
Числа, как и любая другая информация в программе, хранятся в памяти компьютера в двоичном коде. Двоичный код это набор нулей и единиц. Каждый ноль или единица представляют собой единицу измерения информации, которая называется бит.
Согласно Википедии: Бит (от англ. binary digit, двоичное число; также игра слов: англ. bit, кусочек, частица) это единица измерения количества информации. 1 бит информации это символ или сигнал, который может принимать два значения: включено или выключено, да или нет, высокий или низкий, заряженный или незаряженный; в двоичной системе исчисления это 1 (единица) или 0 (ноль).
С какими данными работают побитовые операторы?
Побитовые операции в Java осуществляются только над целыми числами. А целые числа хранятся в памяти компьютера в виде набора битов. Можно сказать, что компьютер переводит любую информацию в двоичную систему счисления (в набор битов) и только потом взаимодействует с ней. Если двоичная система для тебя пока незнакомая территория, загляни в разбор
битов и байтов.
Но как устроена двоичная система счисления?
В десятичной системе счисления у нас есть всего 10 символов: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. С помощью этих символов мы ведем счет. После 9 идет 10, после 19 идет 20, после 99 идет 100, после 749 идет 750. То есть мы используем комбинацию имеющихся 10 символов и можем с их помощью считать «от нуля и до обеда».
В двоичной системе счисления вместо десяти символов есть всего два: 0 и 1. Но комбинируя эти символы по тому же принципу, что и в десятичной системе, можем считать бесконечно долго.
Продемонстрируем счет от 0 до 15 в десятичной системе и в двоичной:
| Десятичная система |
Двоичная система |
| 0 |
0 |
| 1 |
1 |
| 2 |
10 |
| 3 |
11 |
| 4 |
100 |
| 5 |
101 |
| 6 |
110 |
| 7 |
111 |
| 8 |
1000 |
| 9 |
1001 |
| 10 |
1010 |
| 11 |
1011 |
| 12 |
1100 |
| 13 |
1101 |
| 14 |
1110 |
| 15 |
1111 |
Как видим, все не так уж и сложно.
Помимо битов, есть другие знакомые единицы измерения информации:
байты,
килобайты,
мегабайты,
гигабайты и тд.
Ты, наверно, знаешь, что
в 1 байте 8 бит. Что это значит? Это значит, что 8 битов подряд занимают 1 байт. Вот примеры, какими могут быть байты:
00000000 - 1 байт
10110010 - 1 байт
01011011 - 1 байт
Количество возможных неповторяющихся комбинаций битов в одном байте равно 256 (2
8 = 256).
Но вернемся ближе к Java. Есть такой целочисленный тип данных:
byte. Данный тип может принимать значения от -128 до 127 и одно число в памяти компьютера занимает ровно 8 бит, или 1 байт. Одно число этого типа занимает ровно 1
byte памяти компьютера. И здесь названия совпадают не случайно.
Как мы помним, 1 байт может хранить 256 различных значений. И одно число типа
byte может принимать 256 различных значений (128 отрицательных, 127 положительных и 1 ноль).
Каждому значению числа
byte соответствует уникальный набор из восьми битов. Так обстоят дела не только с типом
byte, но и со всеми целочисленными типами. Тип
byte приведен в пример как самый маленький. Ниже в таблице представлены все целочисленные типы Java и занимаемое ими место в памяти:
| Тип |
Размер (бит) |
Диапазон |
byte |
8 бит |
от -128 до 127 |
short |
16 бит |
от -32768 до 32767 |
char |
16 бит |
беззнаковое целое число, представляющее собой символ UTF-16 (буквы и цифры) |
int |
32 бит |
от -2147483648 до 2147483647 |
long |
64 бит |
от -9223372036854775808 до 9223372036854775807 |
Диапазоны и размеры этих типов подробнее разобраны в статье про
типы данных в Java. Рассмотрим тип
int. Он может хранить 2147483648 отрицательных, 2147483647 положительных значений и один ноль. Итого:
2147483648 + 2147483647 + 1 = 4294967296.
Данный тип занимает в памяти компьютера 32 бита. Количество возможных комбинаций из набора 32-ух нулей и единиц равно:
232 = 4294967296.
То же число, что и у количества значений, вмещаемых в тип
int.
Это всего лишь демонстрация взаимосвязи между диапазоном значений типа данных и его размером (количество бит в памяти).
Любое число любого типа в Java можно перевести в двоичную систему счисления. Давай посмотрим, как легко это можно сделать это с помощью Java языка.
Будем учиться на примере типа
int. У данного типа есть свой класс-обертка
Integer. А у него метод
toBinaryString, который и сделает за нас всю работу:
![IntelliJ IDEA: метод Integer.toBinaryString переводит числа в двоичную запись, например 92 в 1011100 и 15 в 1111]()
Вуаля, все не так уж и сложно. Но все-таки кое-что следует уточнить.
int число занимает 32 бита. Но когда мы выводим число 10 в примере выше, мы видим в консоли 1010. Это потому, что ведущие нули не выводятся на печать. Если бы они выводились, вместо 1010 мы бы видели в консоли 00000000000000000000000000001010. Но для удобства восприятия все ведущие нули опускаются.
Не так уж и сложно до тех пор, пока не задашься вопросом: а что с отрицательными числами?
Компьютер воспринимает информацию только в двоичной системе. Получается, что знак минус также необходимо прописывать двоичным кодом. Это можно сделать с помощью прямого или дополнительного кода.
Прямой код
Способ представления чисел в двоичной системе счисления, при котором старший разряд (крайний левый бит) отводится под знак числа. Если число положительное, в крайний левый бит записывается 0, если отрицательное, то 1.
Рассмотрим это на примере 8-ми битного числа:
| Разряд знака числа |
Разряды для кодирования значения числа |
Число в десятичной системе |
| 1 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
-8 |
| 1 |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
-7 |
| 1 |
0 |
0 |
0 |
0 |
1 |
1 |
0 |
-6 |
| 1 |
0 |
0 |
0 |
0 |
1 |
0 |
1 |
-5 |
| 1 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
-4 |
| 1 |
0 |
0 |
0 |
0 |
0 |
1 |
1 |
-3 |
| 1 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
-2 |
| 1 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
-1 |
| 0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
| 0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
1 |
| 0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
2 |
| 0 |
0 |
0 |
0 |
0 |
0 |
1 |
1 |
3 |
| 0 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
4 |
| 0 |
0 |
0 |
0 |
0 |
1 |
0 |
1 |
5 |
| 0 |
0 |
0 |
0 |
0 |
1 |
1 |
0 |
6 |
| 0 |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
7 |
| 0 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
8 |
Подход несложный и в принципе понятный. Однако у него есть недостатки: возникают трудности с выполнением математических операций. К примеру со сложением отрицательных и положительных чисел. Их нельзя складывать, если не провести дополнительные манипуляции.
Дополнительный код
Используя дополнительный код, можно избежать недостатков прямого кода. Для получения дополнительного кода числа есть несложный алгоритм.
Попробуем получить дополнительный код числа -5. Представим это число с помощью дополнительного кода в двоичной системе счисления.
Шаг 1. Получаем представление отрицательного числа с помощью прямого кода. Для -5 это будет 10000101.
Шаг 2. Инвертируем все разряды, кроме разряда знака. Заменим все нули на единицы, а единицы на нули везде, кроме крайнего левого бита.
10000101 => 11111010
Шаг 3. К полученному значению прибавим единицу:
11111010 + 1 = 11111011
Готово. Мы получили значение числа -5 в двоичной системе счисления с использованием дополнительного кода.
Это важно для понимания дальнейшего материала, так как в Java для хранения отрицательных чисел в битах используется дополнительный код.
Типы побитовых операций
Теперь, когда мы разобрались со всеми вводными, поговорим о побитовых операциях в Java.
Побитовая операция осуществляется над целыми числами, и ее результатом будет целое число. В процессе число переводится в двоичную систему, над каждым битом выполняется операция, и результат приводится обратно в десятичную систему.
Список операций в таблице ниже:
| Оператор |
Описание |
~ |
Побитовый унарный оператор NOT |
& |
Побитовое AND |
| |
Побитовое OR |
^ |
Побитовое исключающее OR |
>> |
Сдвиг вправо |
>>> |
Сдвиг вправо с заполнением нулями |
<< |
Сдвиг влево |
Как мы уже выяснили, числа можно представить в виде набора битов. Побитовые операции осуществляют операции как раз над каждым битом такого представления.
Возьмем
NOT,
AND,
OR,
XOR. Вспомним, что недавно мы рассматривали таблицы истинности, только для логических операндов. В данном случае те же операции применяются к каждому биту целого числа.
Побитовый унарный оператор NOT ~
Данный оператор заменяет все нули на единицы, а единицы на нули. Предположим, у нас есть число 10 в десятичной системе счисления. В двоичной системе это число равно 1010. Если применить к данному числу унарный побитовый оператор отрицания, мы получим примерно следующее:
![Схема побитового отрицания: биты 1010 превращаются в 0101]()
Давай взглянем как это выглядит в Java коде:
public static void main(String[] args) {
int a = 10;
System.out.println(" a = " + a + "; binary string: " + Integer.toBinaryString(a));
System.out.println("~a = " + ~a + "; binary string: " + Integer.toBinaryString(~a));
}
Теперь посмотрим, что выведется в консоль:
![IntelliJ IDEA: для a равного 10 выводится двоичная запись 1010, а для отрицания число -11 с записью из тридцати двух бит]()
В первой строке мы получили значение в двоичной системе счисления без ведущих нулей. Хоть мы их не видим, они есть. Об этом говорит вторая строка, в которой все биты трансформировались в обратные. Именно поэтому мы видим так много ведущих единиц. Это бывшие ведущие нули, которые метод
toBinaryString не стал печатать в первой строке.
Вот небольшая программа, которая выводит для наглядности еще и ведущие нули.
![IntelliJ IDEA: программа печатает двоичную запись числа вместе с ведущими нулями, дополняя ее до тридцати двух знаков]()
Побитовый оператор AND
Данный оператор применим к двум числам. Он производит операцию
AND между битами каждого числа. Рассмотрим пример:
![Схема побитового И для чисел 10 и 11: из 1010 и 1011 получается 1010]()
Данная операция осуществляется над двумя числами. Пример в Java коде:
![IntelliJ IDEA: побитовое И для 10 и 11, в консоли Result = 10 и двоичная запись 1010]()
Побитовый оператор OR
OR применим к двум числам. Он производит операцию OR между битами каждого числа:
![Схема побитового ИЛИ для чисел 10 и 11: из 1010 и 1011 получается 1011]()
Теперь взглянем на то, как бы это выглядело в IDEA:
Побитовая операция, исключающее ИЛИ (XOR)
Взглянем на тот же пример, но с новой операцией:
![Схема побитового исключающего ИЛИ для чисел 10 и 11: из 1010 и 1011 получается 0001]()
Пример кода:
![IntelliJ IDEA: побитовое исключающее ИЛИ для 10 и 11, в консоли Result = 1 и двоичная запись 1]()
Побитовый сдвиг влево
Данный оператор применим к двум операндам, то есть в операции
x << y, биты числа
x сдвинутся на
y позиций влево.
Что это значит? Рассмотрим на примере операции
10 << 1
![Схема сдвига влево на один разряд: биты числа 10, то есть 00001010, превращаются в 00010100]()
Результатом операции будет число 20 в десятичной системе.
Как видно из схемы выше, все биты сдвигаются влево на 1. При этой операции значение старшего бита (крайнего левого) теряется. А самый младший бит (крайний правый) заполняется нулем.
Что можно сказать об этой операции?
Сдвигая биты числа X на N битов влево мы умножаем число X на 2N.
Вот пример:
![IntelliJ IDEA: сдвиг влево в цикле, каждая итерация удваивает число: 12, 24, 48, 96 и так далее]()
Но! У нас может измениться знак числа, если бит со значением 1 займет крайнее левое положение.
Если осуществлять сдвиг влево бесконечно долго, число просто превратится в 0. Продемонстрируем пункты 2 и 3:
![IntelliJ IDEA: тридцать два сдвига влево подряд, число растет, потом становится отрицательным и в конце обнуляется]()
Побитовый сдвиг вправо
Данный оператор применим к двум операндам. Т.е. в операции
x >> y, биты числа
x сдвинутся на
y позиций вправо.
Рассмотрим другой пример. Схематично разберем операцию
10 >> 1. Сдвинем все биты числа 10 на одну позицию вправо:
![Схема сдвига вправо на один разряд: биты числа 10, то есть 00001010, превращаются в 00000101]()
При операции сдвига мы теряем правые биты. Они попросту исчезают.
Крайний левый бит это показатель знака числа (0 значит число положительное, 1 значит отрицательное). Поэтому в итоговом значении он ставится таким же, как и в исходном числе. Пример с отрицательным числом:
![Схема сдвига вправо для числа -11: биты 11110101 сдвигаются на разряд, слева дописывается единица знака, получается 11111010]()
Крайний правый бит потерялся, а крайний левый бит скопирован из исходного числа, как почетный показатель знака числа.
Как это все осуществить в IDEA? В принципе, ничего сложного, просто берем и сдвигаем:
![IntelliJ IDEA: сдвиг вправо на один разряд, число 10 дает 5, а число -11 дает -6]()
Теперь. Что можно сказать о числах, над которыми осуществляется сдвиг вправо? Они делятся на 2. Каждый раз, осуществляя сдвиг на один бит вправо мы делим исходное число на 2. Если число нацело на 2 не делится, результат будет округлен в сторону минус бесконечности (в меньшую сторону).
Но это работает, только если мы сдвигаем биты ровно на 1.
А если на 2 бита, делим на 4.
На 3 бита делим на 8.
На 4 бита на 16.
Видишь? Степени двойки…
При сдвиге числа
X на
N битов вправо, мы делим число
X на 2 в степени
N.
Демонстрация:
public class BitOperationsDemo {
public static void main(String[] args) {
for (int i = 1; i <= 10; i++) {
int shiftOperationResult = 2048 >> i;
int devideOperationResult = 2048 / (int) Math.pow(2, i);
System.out.println(shiftOperationResult + " - " + devideOperationResult);
}
}
}
Что тут происходит?
Цикл, в котором переменная i наращивается от 1 до 10.
- Каждую итерацию мы вычисляем 2 значения:
Попарно выводим два полученных значения.
Результат выполнения программы таков:
1024 - 1024
512 - 512
256 - 256
128 - 128
64 - 64
32 - 32
16 - 16
8 - 8
4 - 4
2 - 2
Побитовый сдвиг вправо с заполнением нулями
Если обычный сдвиг битов вправо сохраняет знак числа (старший бит сохраняет свое значение), в случае со сдвигом вправо с заполнением нулями этого не происходит. А происходит заполнение старшего бита нулем. Давай посмотрим, как это выглядит:
![Листинг класса BitOperationsDemo: беззнаковый сдвиг вправо в цикле для числа -1]()
Приоритет операций в Java
Как и в математике, в Java есть приоритет операций. В таблице ниже приведен приоритет (от высшего к низшему) рассмотренных нами операций.
| Операторы |
Приоритет |
| Постфиксные |
expr++ expr-- |
| Унарные |
++expr --expr +expr -expr ~ ! |
| Мультипликативные |
* / % |
| Сложения-вычитания |
+ - |
| Сдвига |
<< >> >>> |
| Отношения |
< > <= >= |
| Сравнения |
== != |
Побитовый AND |
& |
Побитовый XOR |
^ |
Побитовый OR |
| |
Логический AND |
&& |
Логический OR |
|| |
| Присваивания |
= += -= *= /= %= &= ^= |= <<= >>= >>>= |
Полезные примеры использования
Определение четности числа
![Листинг класса OperationsDemo: проверка четности числа через остаток от деления на 2]()
Поиск максимального элемента в массиве
![Листинг класса OperationsDemo: поиск максимального элемента массива циклом for-each]()
Для поиска минимального элемента просто меняем знак сравнения в нужном месте.
Вопросы и ответы
Чем отличаются x++ и ++x?
x++ это постфиксная форма: в выражении сначала используется старое значение переменной и только потом она увеличивается.
++x это префиксная форма: переменная увеличивается сразу, и в выражение попадает уже новое значение. При
int a = 2 запись
int x = a++ даст
x = 2, а
int x = ++a даст
x = 3. Сама
a в обоих случаях станет равна 3.
Что делает оператор % в Java?
Возвращает остаток от целочисленного деления.
24 % 7 даст 3, потому что 24 это 7 умножить на 3 плюс 3. Чаще всего его используют для проверки четности: если
a % 2 == 0, число четное.
Чем & отличается от &&?
&& сокращенный: если левый операнд уже
false, правый не вычисляется вообще.
& вычисляет оба операнда всегда. Кроме того,
& применим и к целым числам, где работает по каждому биту, а
&& только к
boolean.
Чем >> отличается от >>>?
Оба сдвигают биты вправо.
>> сохраняет знак: слева дописывается тот же бит, что был старшим, поэтому
-11 >> 1 даст -6.
>>> всегда дописывает слева нули, поэтому у отрицательного числа результат превращается в большое положительное.
Почему сдвиг влево иногда меняет знак числа?
За знак целого числа отвечает старший бит. При сдвиге влево все биты уезжают в его сторону, и если на место старшего попадет единица, число станет отрицательным. Если сдвигать дальше, значащие биты вытеснятся совсем и число превратится в 0.
Какой приоритет у операторов в Java?
От высшего к низшему: постфиксные
++ и
--, затем унарные, затем
*,
/,
%, затем
+ и
-, затем сдвиги, затем операторы отношения и сравнения, затем побитовые
&,
^,
|, затем
&& и
||, и в самом конце присваивание. Порядок меняется скобками.
Читайте также
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
битахбайтах используется дополнительный код."