W3docs

Побитовые операторы Java

Управляйте отдельными битами в Java с помощью операторов &, |, ^, ~, <<, >> и >>>.

В большинстве Java-кода нет необходимости работать с отдельными битами. Но иногда — при упаковке флагов в один int, чтении двоичного формата файла, вычислении хеша, работе с битовыми масками прав доступа — нужно манипулировать значениями на уровне битов. Побитовые операторы Java унаследованы из C: &, |, ^, ~ и три оператора сдвига. Они отличаются от логических операторов && и ||, которые используют ленивые вычисления и работают только с boolean значениями — побитовые операторы воздействуют на каждый бит целого числа.

Операторы

ОператорНазваниеЧто делает
&AND (И)бит равен 1, только если оба бита равны 1
|OR (ИЛИ)бит равен 1, если хотя бы один бит равен 1
^XOR (исключающее ИЛИ)бит равен 1, если биты различаются
~NOT (дополнение)инвертирует все биты
<<сдвиг влевосдвигает биты влево, справа заполняет нулями
>>знаковый сдвиг вправосдвигает вправо, слева заполняет знаковым битом
>>>беззнаковый сдвиг вправосдвигает вправо, слева заполняет нулями

Все операторы работают с операндами типа int и long. Типы byte, short и char сначала повышаются до int.

Двоичные литералы (0b...) позволяют наглядно увидеть битовые шаблоны:

int a = 0b1100;   // 12
int b = 0b1010;   // 10

System.out.println(Integer.toBinaryString(a & b));   // 1000  (8)
System.out.println(Integer.toBinaryString(a | b));   // 1110  (14)
System.out.println(Integer.toBinaryString(a ^ b));   // 110   (6)

Обратите внимание, что Integer.toBinaryString не выводит ведущие нули — 6 выводится как 110, а не 0110. Если нужна фиксированная ширина для отображения, добавьте дополнение самостоятельно.

NOT — ~

~ инвертирует каждый бит, включая знаковый. Для 32-битного int это дополнение до двух: ~x равно -x - 1:

System.out.println(~0);    // -1
System.out.println(~5);    // -6
System.out.println(~-1);   // 0

Сдвиги

<< сдвигает влево, умножая на степени 2:

System.out.println(1 << 0);   // 1
System.out.println(1 << 1);   // 2
System.out.println(1 << 4);   // 16

>> сдвигает вправо, сохраняя знак — полезно для деления знаковых целых чисел:

System.out.println(16 >> 2);   // 4
System.out.println(-16 >> 2);  // -4   — sign extended

>>> сдвигает вправо и всегда заполняет нулями — полезен, когда вы работаете с int как с беззнаковыми битами:

System.out.println(-1 >>> 28);  // 15
System.out.println(-1 >> 28);   // -1

Практическое применение

Битовые маски флагов

Упакуйте несколько флагов да/нет в один int:

final int READ    = 1 << 0;  // 0001
final int WRITE   = 1 << 1;  // 0010
final int EXECUTE = 1 << 2;  // 0100

int perms = READ | WRITE;             // set both

boolean canRead    = (perms & READ) != 0;     // true
boolean canExecute = (perms & EXECUTE) != 0;  // false

perms |= EXECUTE;       // grant execute
perms &= ~WRITE;        // revoke write
perms ^= READ;          // toggle read

Это та же идея, что и права доступа к файлам в Unix.

Умножение или деление на степени 2

x << n — это x * 2ⁿ; x >> n — это x / 2ⁿ (для неотрицательного x):

int doubled = x << 1;
int halved  = x >> 1;

Компилятор обычно сам оптимизирует обычное умножение и деление на константные степени 2 до сдвигов, поэтому пишите то, что понятнее.

Обмен двух int без временной переменной

Классический трюк с XOR:

int a = 5, b = 3;
a ^= b;
b ^= a;
a ^= b;
System.out.println(a + " " + b);   // 3 5

Красиво, но редко стоит использовать вместо временной переменной — современные компиляторы отлично справляются с вариантом через временную переменную.

Демонстрация

java— editable, runs on the server

Когда использовать эти операторы, а когда EnumSet

Для небольшого фиксированного набора флагов в современном Java обычно лучше использовать EnumSet<MyFlag> — он понятнее и столь же эффективен: внутри хранит значения enum в виде одной битовой маски типа long, поэтому вы получаете читаемость Set<MyFlag> с быстрыми побитовыми операциями:

enum Permission { READ, WRITE, EXECUTE }

EnumSet<Permission> perms = EnumSet.of(Permission.READ, Permission.WRITE);
perms.add(Permission.EXECUTE);
perms.contains(Permission.READ);   // true

Прибегайте к сырым побитовым операциям только при работе с бинарными форматами, аппаратными регистрами или горячими путями, где важна упаковка в int.

Что дальше

Java Strings — ссылочный тип, с которым вы будете работать чаще всего.

Практика

Практика
Чему равно выражение 1 << 4?
Чему равно выражение 1 << 4?
Was this page helpful?