Побитовые операторы
Любое целое число хранится в памяти как последовательность битов — нулей и единиц. Побитовые операторы работают не с числом целиком, а с каждым его битом по отдельности. Их применяют для флагов, масок, кодирования данных и быстрых вычислений.
Двоичная запись
Метод Integer.toBinaryString() показывает число в двоичном виде. А в коде двоичное число записывают с префиксом 0b:
void main() {
IO.println(Integer.toBinaryString(5));
IO.println(Integer.toBinaryString(12));
IO.println(0b1010);
}Результат:
101
1100
10Обратно строку из нулей и единиц в число превращает Integer.parseInt("1010", 2) — второй аргумент задаёт систему счисления.
И, ИЛИ, исключающее ИЛИ
Операторы сравнивают биты двух чисел попарно. & (И) даёт 1, только если оба бита равны 1; | (ИЛИ) — если хотя бы один равен 1; ^ (исключающее ИЛИ) — если биты разные:
void main() {
int a = 0b1100;
int b = 0b1010;
IO.println(Integer.toBinaryString(a & b));
IO.println(Integer.toBinaryString(a | b));
IO.println(Integer.toBinaryString(a ^ b));
}Результат:
1000
1110
110Не путайте их с логическими && и || из урока об операторах: те работают с boolean и не вычисляют правую часть, если ответ уже известен.
Инверсия ~
Оператор ~ переворачивает каждый бит: 0 становится 1, а 1 — нулём. Поскольку старший бит int отвечает за знак, результат получается отрицательным: ~n всегда равно -n - 1:
void main() {
IO.println(~5);
IO.println(Integer.toBinaryString(~5));
}Результат:
-6
11111111111111111111111111111010Сдвиги
<< сдвигает биты влево, дописывая справа нули, — это умножение на 2 для каждой позиции. >> сдвигает вправо с сохранением знака — деление на 2. >>> тоже сдвигает вправо, но слева всегда дописывает нули, даже у отрицательных чисел:
void main() {
IO.println(1 << 3);
IO.println(40 >> 2);
IO.println(-16 >> 2);
IO.println(-16 >>> 28);
}Результат:
8
10
-4
15Флаги и маски
Каждый бит числа может хранить отдельный признак «да/нет» — флаг. Число с нужными битами называется маской. | включает флаг, & проверяет его, а & ~ выключает:
void main() {
final int READ = 0b001;
final int WRITE = 0b010;
final int EXECUTE = 0b100;
int access = READ | WRITE;
IO.println("Чтение: " + ((access & READ) != 0));
IO.println("Запуск: " + ((access & EXECUTE) != 0));
access = access & ~WRITE;
IO.println(Integer.toBinaryString(access));
}Результат:
Чтение: true
Запуск: false
1В одном int помещается 32 таких флага. В обычном коде вместо масок чаще используют EnumSet — о нём урок в главе о коллекциях.
Частая ошибка
Сравнение != выполняется раньше, чем &. Без скобок Java пытается вычислить WRITE != 0, а потом применить & к числу и boolean:
void main() {
int access = 0b011;
int WRITE = 0b010;
if (access & WRITE != 0) {
IO.println("Можно писать");
}
}Результат:
Main.java:4: error: bad operand types for binary operator '&'
if (access & WRITE != 0) {
^
first type: int
second type: boolean
1 error
error: compilation failedВсегда берите побитовую часть в скобки: (access & WRITE) != 0.