Java определяет несколько побитовых операций, которые могут применяться к целочисленным типам: long, int, short, char и byte. Эти операции воздействуют на отдельные биты операндов. Они перечислены в табл. 4.2.
Таблица 4.2. Побитовые операции в Java
| Операция | Описание |
| ~ | Побитовая унарная операция NOT (НЕ) |
| & | Побитовое AND (И) |
| | | Побитовое OR (ИЛИ) |
| ^ | Побитовое исключающее OR |
| »» | Сдвиг вправо |
| » | Сдвиг вправо с заполнением нулями |
| « | Сдвиг влево |
| &= | Побитовое AND с присваиванием |
| |= | Побитовое OR с присваиванием |
| ^= | Побитовое исключающее OR с присваиванием |
| »= | Сдвиг вправо с присваиванием |
| >»= | Сдвиг вправо с заполнением нулями с присваиванием |
| «= | Сдвиг влево с присваиванием |
Поскольку побитовые операции манипулируют битами в целочисленном значении, важно понимать, какое влияние подобные манипуляции могут оказывать на значение. В частности, важно знать, как среда Java хранит целочисленные значения, и как она представляет отрицательные числа. Поэтому, прежде чем продолжить рассмотрение операций, кратко рассмотрим эти два вопроса.
Все целочисленные типы представляются двоичными числами различной длины. Например, значение типа byte, равное 42, в двоичном представлении имеет вид 00101010.
Все целочисленные типы (за исключением char) — целочисленные типы со знаком. Это означает, что они могут представлять как положительные, так и отрицательные значения. В Java применяется кодирование, называемое двоичным дополнением, при котором отрицательные числа представляются посредством инвертирования всех битов значения (изменения 1 на 0 и наоборот) и последующего добавления 1 к результату. Например, -42 представляется путем инвертирования все битов в двоичном представлении числа 42, что дает значение 11010101, и добавления 1, что приводит к значению 11010110, или -42. Чтобы декодировать отрицательное число, необходимо вначале инвертировать все биты, а затем добавить 1 к результату. Например, инвертирование значения -42, или 11010110, приводит к значению 00101001, или 41, после добавления 1 к которому мы получаем 42.
Причина, по которой в Java (и большинстве других компьютерных языков) применяют двоичное дополнение, становится понятной при рассмотрении перехода через нуль. Если речь идет о значении типа byte, ноль представляется значением 00000000. В случае применения единичного дополнения простое инвертирование всех битов создает значение, равное 11111111, которое представляет отрицательный ноль. Проблема в том, что отрицательный ноль — недопустимое значение в целочисленной математике. Применение двоичного дополнения для представления отрицательных значений позволяет решить эту проблему. При этом к дополнению добавляется 1, что приводит к числу 100000000. Единичный бит оказывается сдвинутым влево слишком далеко, чтобы умещаться в значении типа byte. Тем самым достигается требуемое поведение, когда -0 эквивалентен 0, а 11111111 — код значения, равного -1. Хотя в приведенном примере мы использовали значение типа byte, тот же базовый принцип применяется и ко всем целочисленным типам Java.
Поскольку в Java для хранения отрицательных значений используется двоичное дополнение — и поскольку в Java все целочисленные значения являются значениями со знаком — применение побитовых операций может легко приводить к неожиданным результатам. Например, установка самого старшего бита равным 1 может привести к тому, что результирующее значение будет интерпретироваться как отрицательное число, независимо от того, к этому результату вы стремились или нет. Во избежание неприятных сюрпризов следует помнить, что независимо от того, как он был установлен, старший бит определяет знак целого числа.