Побитовые операции Java определяет несколько побитовых операций, которые могут применяться к целочисленным типам: long, int, short, char и byte. Эти операции воздействуют на отдельные биты операндов.
Побитовая унарная операция NOT (НЕ)
& Побитовое AND (И) | Побитовое OR (ИЛИ) ^ Побитовое исключающее OR >> Сдвиг вправо >>> Сдвиг вправо с заполнением нулями << Сдвиг влево &= Побитовое AND с присваиванием |= Побитовое OR с присваиванием ^= Побитовое исключающее OR с присваиванием >>= Сдвиг вправо с присваиванием >>>= Сдвиг вправо с заполнением нулями с присваиванием <<= Сдвиг влево с присваиванием Поскольку побитовые операции манипулируют битами в целочисленном значении, важно понимать, какое влияние подобные манипуляции могут оказывать на значение.
В частности, важно знать, как среда Java хранит целочисленные значения, и как она представляет отрицательные числа. Поэтому, прежде чем продолжить рассмотрение операций, кратко рассмотрим эти два вопроса.
Все целочисленные типы представляются двоичными числами различной длины. Например, значение типа byte, равное 42, в двоичном представлении имеет вид 00101010, в котором каждая позиция представляет степень двух, начиная с 20 в крайнем справа бите. Бит в следующей позиции будет представлять 21, или 2, следующий – 22, или 4, затем 8, 16, 32 и т.д. Таким образом, двоичное представление числа 42 содержит единичные биты в позициях 1, 3 и 5 (начиная с 0, крайней справа позиции). Следовательно,
42=21+23+25=2+8+32.
Все целочисленные типы (за исключением 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 может привести к тому, что результирующее значение будет интерпретироваться как отрицательное число, независимо от того, к этому результату вы стремились или нет. Во избежание неприятных сюрпризов следует помнить, что независимо от того, как он был установлен, старший бит определяет знак целого числа.
Побитовые логические операции Побитовые логические операции – это &, |, ^ и . В ходе ознакомления с последующим материалом помните, что побитовые операции применяются к каждому отдельному биту каждого операнда.
Называемая также операцией побитового дополнения, унарная операция NOT (НЕ), , инвертирует все биты операнда. Например, число 42, которое имеет следующую последовательность битов:
00101010
в результате применения операции NOT преобразуется в:
11010101
Значение бита, полученное в результате побитовой операции AND, &, равно 1, если соответствующие биты в операндах также равны 1. Во всех остальных случаях значение результирующего бита равно 0. Например:
00101010 42
& 00001111 15
----------------------
00001010 10
Результирующий бит, полученный в результате выполнения операции OR, |, равен 1, если соответствующий бит в любом из операторов равен 1, как показано в следующем примере:
00101010 42
| 00001111 15
---------------------
00101111 47
Результирующий бит, полученный в результате выполнения операции XOR, ^, равен 1, если соответствующий бит только в одном из операндов равен 1. Во всех других случаях результирующий бит равен 0. Обратите внимание на инвертирование последовательности битов числа 42 во всех случаях, когда второй операнд содержит бит, равный 1. Во всех случаях, когда второй операнд содержит бит, равный 0, значение первого операнда остается неизменным. Это свойство пригодится при выполнении некоторых операций с битами.
00101010 42
^ 00001111 15
--------------------
00100101 37
В следующей программе демонстрируется применение побитовых логических операций.
// Демонстрация побитовых логических операций.
class BitLogic {
public static void main(String args[]) {
String binary[] = {
"0000", "0001", "0010", "0011", "0100", "0101", "0110", "0111",
"1000", "1001", "1010", "1011", "1100", "1101", "1110", "1111"
};
int a = 3; // 0 + 2 + 1 или 0011 в двоичном представлении
int b = 6; // 4 + 2 + 0 или 0110 в двоичном представлении
int c = a | b;
int d = a & b;
int e = a ^ b;
int f = (a & b) | (a & b);
int g = a & 0x0f;
BookNew_JAVA-7.indb 100 02.06.2007 1:06:41
Глава 4. Операции 101
System.out.println(" a = " + binary[a]);
System.out.println(" b = " + binary[b]);
System.out.println(" a|b = " + binary[c]);
System.out.println(" a&b = " + binary[d]);
System.out.println(" a^b = " + binary[e]);
System.out.println("a&b|a&b = " + binary[f]);
System.out.println(" a = " + binary[g]);
}
}
В этом примере последовательности битов переменных a и b представляют все четыре возможных комбинации двух двоичных цифр: 0-0, 0-1, 1-0 и 1-1. О действии операций | и & на каждый бит можно судить по результирующим значениям переменных c и d.
Значений, присвоенные переменных e и f, иллюстрируют действие операции ^. Массив строк binary содержит читабельные двоичные представления чисел от 0 до 15. В этом примере массив индексирован, что позволяет увидеть двоичное представление каждого результирующего значения. Массив построен так, чтобы соответствующее строковое представление двоичного значения n хранилось в элементе массива binary[n]. Чтобы его можно было выводить посредством массива binary, значение a уменьшается до значения, меньшего 16, путем его объединения со значением 0x0f (0000 1111 в двоичном представлении) операцией AND. Вывод этой программы имеет следующий вид:
a = 0011
b = 0110
a|b = 0111
a&b = 0010
a^b = 0101
a&b|a&b = 0101
a = 1100
|