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

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 может привести к тому, что результирующее значение будет интерпретироваться как отрицательное число, независимо от того, к этому результату вы стремились или нет. Во избежание неприятных сюрпризов следует помнить, что независимо от того, как он был установлен, старший бит определяет знак целого числа.




Rambler's Top100