Битовый сдвиг java. Операции над примитивными типами в Java. Составные арифметические операции с присваиванием

Для целых числовых типов данных - long, int, short, char и byte - определен дополнительный набор операторов, с помощью которых можно проверять и модифицировать состояние отдельных битов соответствующих значений. В таблице 4.2 приведена сводка таких операторов. Операторы битовой арифметики работают с каждым битом как с самостоятельной величиной.

Таблица 4.2. Операторы битовой арифметики

Оператор

Результат

Оператор

Результат

побитовое И (AND)

Побитовое И (AND) с присваиванием

побитовое ИЛИ (OR)

побитовое ИЛИ (OR) с присваиванием

побитовое исключающее ИЛИ (XOR)

побитовое исключающее ИЛИ (XOR) с присваиванием

сдвиг вправо

сдвиг вправо с присваиванием

сдвиг вправо с заполнением нулями

сдвиг вправо с заполнением нулями с присваиванием

сдвиг влево

сдвиг влево с присваиванием

побитовое унарное отрицание (NOT)

В таблице 4.3 показано, как каждый из операторов битовой арифметики воздействует на возможные комбинации битов своих операндов.

Таблица 4.3

Сдвиг влево

Оператор << выполняет сдвиг влево всех битов своего левого операнда на число позиций, заданное правым операндом. При этом часть битов в левых разрядах выходит за границы и теряется, а соответствующие правые позиции заполняются нулями.

Сдвиг вправо

Оператор >> означает в языке Java сдвиг вправо. Он перемещает все биты своего левого операнда вправо на число позиций, заданное правым операндом. Когда биты левого операнда выдвигаются за самую правую позицию слова, они теряются. При сдвиге вправо освобождающиеся старшие (левые) разряды сдвигаемого числа заполняются предыдущим содержимым знакового разряда. Сделано это для того, чтобы при сдвиге вправо числа сохраняли свой знак.

Беззнаковый сдвиг вправо

Часто требуется, чтобы при сдвиге вправо расширение знакового разряда не происходило, а освобождающиеся левые разряды заполнялись бы нулями. С этой целью используется оператор беззнакового сдвига вправо >>>.

4.3. Операторы отношений

Для того чтобы можно было сравнивать два значения, в Java имеется набор операторов, описывающих отношение и равенство. Список таких операторов приведен в таблице 4.4.

Таблица 4.4

Оператор

Результат

равно

не равно

больше

меньше

больше или равно

меньше или равно

Значения любых типов, включая целые и вещественные числа, символы, логические значения и ссылки, можно сравнивать, используя оператор проверки на равенство == и неравенство!=. Обратите внимание - в языке Java проверка на равенство обозначается последовательностью (==). Один знак (=) - это оператор присваивания.

Операторы отношения могут применяться только к операндам числовых типов. С их помощью можно работать с целыми, вещественными и символьными типами. Каждый из операторов отношения возвращает результат типа boolean, т.е. либоtrue , либоfalse .

В Java есть операторы сдвига. Операторы << и >> позаимствованы из С/C++. Кроме того, Java обладает своим новым оператором сдвига >>>.

Операторы сдвига присущи системам, которые могут выравнивать биты, прочтённые из IO портов или зартсываемые в IO порты. Это также быстрое умножение или деление на степень двойки. Преимущество операторов сдвига в Java - это независимость от платформы. Поэтому вы можете использовать их не беспокоясь ни о чём.

Основы сдвига

Сдвиг - это, по сути, простейшая операция: мы берём последовательность битов и двигаем её влево или вправо. Больше всего конфуза вызывает оператор >>>. Но о нём мы поговорим чуть позже.

Операторы сдвига могут применяться лишь к целым числам, то есть к типам int или long . Следующая таблица иллюстрирует базовый механизм сдвига.

Таблица 1: Идея сдвига

Исходные данные
Бинарное представление 00000000 00000000 00000000 11000000
Сдвиг влево на 1 бит 00000000 00000000 00000001 1000000?
Сдвиг вправо на бит ?0000000 00000000 00000000 01100000 0
Сдвиг влево на 4 бита 0000 00000000 00000000 00001100 0000????
Исходные данные
Бинарное представление 11111111 11111111 11111111 01000000
Сдвиг влево на 1 бит 11111111 11111111 11111110 1000000?
Сдвиг вправо на бит ?1111111 11111111 11111111 10100000 0

Таблица показывает фундаментальную идею сдвига: перемещение битов относительно их позиций. Это как очередь в магазине: как только один человек совершил покупку и отшёл, вся очередь сдвинулась и позиции всех участников очереди изменились.

Однако, глядя на таблицу, возникают три вопроса вопроса:

  1. Что происходит, если мы сдвигаем влево и при этом часть бинарной записи выходит за границу слева, а часть - остаётся пустой справа?
  2. Что происходит, когда справа - выход за границы, а слева - пустое место?
  3. Какое истинное значение принимает знак "?"?.

Ответим на часть этих вопросов. Биты, вышедшие за границы, просто теряются. Мы о них забываем.

В некоторых языках, типа ассемблер, есть операция ротации , когда при сдвиге вышедшие за границы биты не теряются, но ставятся на освободившееся место (вместо вопросиков). Однако языки высокого уровня, типа Java, не имеют в своём арсенале такой операции.

Сдвиг отрицательных чисел

Ответ на вопрос о значении символов "?" в приведенной выше таблице требует отдельного рассмотрения.

В случае сдвига влево << и беззнакового сдвига вправо >>> новые биты просто устанавливаются в ноль. В случае сдвига вправо со знаком >> новые биты принимают значение старшего (самого левого) бита перед сдвигом. Следующая таблица демонстрирует это:

Таблица 2: Сдвиг положительных и отрицательных чисел

Исходные данные
Бинарное представление 00000000 00000000 00000000 11000000
Сдвиг вправо на 1 бит 00000000 00000000 00000000 01100000
Сдвиг вправо на 7 бит 00000000 00000000 00000000 00000001
Исходные данные
Бинарное представление 11111111 11111111 11111111 01000000
Сдвиг вправо на 1 бит 11111111 11111111 11111111 10100000
Сдвиг вправо на 7 бит 11111111 11111111 11111111 11111110

Заметьте: в том, случае, где старший бит был 0 перед сдвигом, новые биты стали тоже 0. Там где старший бит перед сдвигом был 1, новые биты тоже заполнились 1.

Это правило может показаться странным на первый взгляд. Но оно имеет под собой очень серьёзное обоснование. Если мы сдвигаем бинарное число влево на одну позицию, то в десятичной записи мы умножаем его на два. Если мы сдвигаем влево на n позиций, то умножение происходит на 2 n , то есть на 2, 4, 8, 16 и т.д.

Сдвиг вправо даёт деление на степени двойки. При этом, добавление слева нулей на появившиеся биты на самом деле даёт деление на степени двойки лишь в случае положительных чисел. Но для отрицательных чисел всё совсем по другому!

Как известно, старший бит отрицательных чисел равен единице, 1. Для того, чтобы сохранить старшинство единицы при сдвиге, то есть сохранить отрицательный знак результата деления отрицательного числа на положительное (степень двойки), нам нужно подставлять единицы на освободившиеся места.

Если мы посмотрим на Таблицу 2, то заметим, что 192, сдвинутое на 1 бит вправо - это 192/2=96, а сдвинутое на 7 битов вправо - это 192/2 7 =192/128=1 по законам целочисленной арифметики. С другой стороны, -192 сдвинутое на 1 бит вправо - это 192/2=-96 и т.д.

Есть, однако пример, когда реультат сдвига вправо отличается от результата целочисленного деления на 2. Это случай, когда аргумент = -1. При целочисленном делении мы имеем: -1/2=0. Но результат сдвига вправо нам даёт -1. Это можно трактовать так: целочисленное деление округляет к нулю, а сдвиг округляет к -1.

Таким образом, сдвиг вправо имеет две ипостаси: одна (>>>) просто сдвигает битовый паттерн "в лоб", а другая (>>) сохраняет эквивалентность с операцией деления на 2.

Зачем же Java потребовался беззнаковый сдвиг вправо (сдвиг "в лоб"), когда ни в С, ни в С++ его не существует? Ответ прост, потому что в С и С++ сдвиг всегда беззнаковый . То есть >>>> в Java - это и есть сдвиг вправо в C и C++. Но, поскольку в Java все численные типы со знаком (за исключением char ), то и результаты сдвигов должны иметь знаки.

Сокращение (reduction) правого операнда

На самом деле у операторов сдвига есть правый операнд - число позиций, на которое нужно произвести сдвиг. Для корректного сдвига это число должно быть меньше, чем количество битов в результате сдвига. Если число типа int (long) , то сдвиг не может быть сделан более, чем на 32 (64) бита.

Оператор же сдвига не делает никаких проверок данного условия и допускает операнды, его нарушающие. При этом правый операнд сокращается по модулю от нужного количества битов. Например, если вы захотите сдвинуть целое число на 33 бита, то сдвиг произойдёт на 33%32=1 бит. В результатае такого сдвига мы легко можем получить аномальные результаты, то есть результаты, которых мы не ожидали. Например, при сдвиге на 33 бита мы ожидаем получить 0 или -1 (в знаковой арифметике). Но это не так.

Почему Java сокращает правый операнд оператора сдвига или грустная история о заснувшем процессоре

Одной из главной причин введения сокращения было то, что процессоры сами сокращают подобным образом правый операнд оператора сдвига. Почему?

Несколько лет назад был создан мощнейший процессор с длинными регистрами и операциями ротации и сдвигам на любое количество битов. Именно потому, что регистры были длинными, корректное выполнение этих операций требовало несколько минут.

Основным применением данных процессоров был контроль систем реального времени. В данных системах самый быстрый ответ на внешнее событие должно занимать не более задержки на прерывание (interrupt latency ). Отдельные инскрукции таких процессоров были неделимы. Поэтому выполнение длинных операций (сдвига на несколько бит и ротации) нарушало эффективную работу процессора.

Следующая версия процессора имплементировала эти операции уже по-другому: размер правого операнда сократился. Задержка на прерывание восстанавилась. И многие процессоры переняли данную практику.

Арифметическое распространение (promotion ) операндов

Апифметическое распространение операндов происходит перед применением оперции сдвига и гарантирует, что операнды по крайней мере типа int . Это явление имеет особый эффект на беззнаковый сдвиг вправо, когда сдвигаемое число меньше, чем int : мы получаем не тот результат, который ожидали.

Следующая таблица показывает пример аномалии:

Таблица 3: Арифметическое распространение для беззнакового сдвига вправо, когда операнд меньше, чем int

Исходные данные (-64 в десятичной записи)
Распространение до int 11111111 11111111 11111111 11000000
Сдвиг вправо на 4 битa 00001111 11111111 11111111 11111100
Сокращение до байта 11111100
Ожидаемый результат был 00001100

Последнее обновление: 30.10.2018

Побитовые или поразрядные операции выполняются над отдельными разрядами или битами чисел. В данных операциях в качестве операндов могу выступать только целые числа.

Каждое число имеет определенное двоичное представление. Например, число 4 в двоичной системе 100, а число 5 - 101 и так далее.

К примеру, возьмем следующие переменны:

Byte b = 7; // 0000 0111 short s = 7; // 0000 0000 0000 0111

Тип byte занимает 1 байт или 8 битов, соответственно представлен 8 разрядами. Поэтому значение переменной b в двоичном коде будет равно 00000111 . Тип short занимает в памяти 2 байта или 16 битов, поэтому число данного типа будет представлено 16 разрядами. И в данном случае переменная s в двоичной системе будет иметь значение 0000 0000 0000 0111 .

Для записи чисел со знаком в Java применяется дополнительный код (two’s complement), при котором старший разряд является знаковым. Если его значение равно 0, то число положительное, и его двоичное представление не отличается от представления беззнакового числа. Например, 0000 0001 в десятичной системе 1.

Если старший разряд равен 1, то мы имеем дело с отрицательным числом. Например, 1111 1111 в десятичной системе представляет -1. Соответственно, 1111 0011 представляет -13.

Логические операции

Логические операции над числами представляют поразрядные операции. В данном случае числа рассматриваются в двоичном представлении, например, 2 в двоичной системе равно 10 и имеет два разряда, число 7 - 111 и имеет три разряда.

    & (логическое умножение)

    Умножение производится поразрядно, и если у обоих операндов значения разрядов равно 1, то операция возвращает 1, иначе возвращается число 0. Например:

    Int a1 = 2; //010 int b1 = 5;//101 System.out.println(a1&b1); // результат 0 int a2 = 4; //100 int b2 = 5; //101 System.out.println(a2 & b2); // результат 4

    В первом случае у нас два числа 2 и 5. 2 в двоичном виде представляет число 010, а 5 - 101. Поразрядное умножение чисел (0*1, 1*0, 0*1) дает результат 000.

    Во втором случае у нас вместо двойки число 4, у которого в первом разряде 1, так же как и у числа 5, поэтому здесь результатом операции (1*1, 0*0, 0 *1) = 100 будет число 4 в десятичном формате.

    | (логическое сложение)

    Данная операция также производится по двоичным разрядам, но теперь возвращается единица, если хотя бы у одного числа в данном разряде имеется единица (операция "логическое ИЛИ"). Например:

    Int a1 = 2; //010 int b1 = 5;//101 System.out.println(a1|b1); // результат 7 - 111 int a2 = 4; //100 int b2 = 5;//101 System.out.println(a2 | b2); // результат 5 - 101

    ^ (логическое исключающее ИЛИ)

    Также эту операцию называют XOR, нередко ее применяют для простого шифрования:

    Int number = 45; // 1001 Значение, которое надо зашифровать - в двоичной форме 101101 int key = 102; //Ключ шифрования - в двоичной системе 1100110 int encrypt = number ^ key; //Результатом будет число 1001011 или 75 System.out.println("Зашифрованное число: " +encrypt); int decrypt = encrypt ^ key; // Результатом будет исходное число 45 System.out.println("Расшифрованное число: " + decrypt);

    Здесь также производятся поразрядные операции. Если у нас значения текущего разряда у обоих чисел разные, то возвращается 1, иначе возвращается 0. Например, результатом выражения 9^5 будет число 12. А чтобы расшифровать число, мы применяем обратную операцию к результату.

    ~ (логическое отрицание)

    Поразрядная операция, которая инвертирует все разряды числа: если значение разряда равно 1, то оно становится равным нулю, и наоборот.

    Byte a = 12; // 0000 1100 System.out.println(~a); // 1111 0011 или -13

Операции сдвига

Операции сдвига также производятся над разрядами чисел. Сдвиг может происходить вправо и влево.

    a<

    a>>b - смещает число a вправо на b разрядов. Например, 16>>1 сдвигает число 16 (которое в двоичной системе 10000) на один разряд вправо, то есть в итоге получается 1000 или число 8 в десятичном представлении.

    a>>>b - в отличие от предыдущих типов сдвигов данная операция представляет беззнаковый сдвиг - сдвигает число a вправо на b разрядов. Например, выражение -8>>>2 будет равно 1073741822.

Таким образом, если исходное число, которое надо сдвинуть в ту или другую строну, делится на два, то фактически получается умножение или деление на два. Поэтому подобную операцию можно использовать вместо непосредственного умножения или деления на два, так как операция сдвига на аппаратном уровне менее дорогостоящая операция в отличие от операции деления или умножения.

Последнее обновление: 30.10.2018

Большинство операций в Java аналогичны тем, которые применяются в других си-подобных языках. Есть унарные операции (выполняются над одним операндом), бинарные - над двумя операндами, а также тернарные - выполняются над тремя операндами. Операндом является переменная или значение (например, число), участвующее в операции. Рассмотрим все виды операций.

В арифметических операциях участвуют числами. В Java есть бинарные арифметические операции (производятся над двумя операндами) и унарные (выполняются над одним операндом). К бинарным операциям относят следующие:

    операция сложения двух чисел:

    Int a = 10; int b = 7; int c = a + b; // 17 int d = 4 + b; // 11

    операция вычитания двух чисел:

    Int a = 10; int b = 7; int c = a - b; // 3 int d = 4 - a; // -6

    операция умножения двух чисел

    Int a = 10; int b = 7; int c = a * b; // 70 int d = b * 5; // 35

    операция деления двух чисел:

    Int a = 20; int b = 5; int c = a / b; // 4 double d = 22.5 / 4.5; // 5.0

    При делении стоит учитывать, так как если в операции участвуют два целых числа, то результат деления будет округляться до целого числа, даже если результат присваивается переменной float или double:

    Double k = 10 / 4; // 2 System.out.println(k);

    Чтобы результат представлял числос плавающей точкой, один из операндов также должен представлять число с плавающей точкой:

    Double k = 10.0 / 4; // 2.5 System.out.println(k);

    получение остатка от деления двух чисел:

    Int a = 33; int b = 5; int c = a % b; // 3 int d = 22 % 4; // 2 (22 - 4*5 = 2)

Также есть две унарные арифметические операции, которые производятся над одним числом: ++ (инкремент) и -- (декремент). Каждая из операций имеет две разновидности: префиксная и постфиксная:

    ++ (префиксный инкремент)

    Предполагает увеличение переменной на единицу, например, z=++y (вначале значение переменной y увеличивается на 1, а затем ее значение присваивается переменной z)

    Int a = 8; int b = ++a; System.out.println(a); // 9 System.out.println(b); // 9

    ++ (постфиксный инкремент)

    Также представляет увеличение переменной на единицу, например, z=y++ (вначале значение переменной y присваивается переменной z, а потом значение переменной y увеличивается на 1)

    Int a = 8; int b = a++; System.out.println(a); // 9 System.out.println(b); // 8

    -- (префиксный декремент)

    уменьшение переменной на единицу, например, z=--y (вначале значение переменной y уменьшается на 1, а потом ее значение присваивается переменной z)

    Int a = 8; int b = --a; System.out.println(a); // 7 System.out.println(b); // 7

    -- (постфиксный декремент)

    z=y-- (сначала значение переменной y присваивается переменной z, а затем значение переменной y уменьшается на 1)

    Int a = 8; int b = a--; System.out.println(a); // 7 System.out.println(b); // 8

Приоритет арифметических операций

Одни операции имеют больший приоритет чем другие и поэтому выполняются вначале. Операции в порядке уменьшения приоритета:

++ (инкремент), -- (декремент)

* (умножение), / (деление), % (остаток от деления)

+ (сложение), - (вычитание)

Приоритет операций следует учитывать при выполнении набора арифметических выражений:

Int a = 8; int b = 7; int c = a + 5 * ++b; System.out.println(c); // 48

Вначале будет выполняться операция инкремента ++b , которая имеет больший приоритет - она увеличит значение переменной b и возвратит его в качестве результата. Затем выполняется умножение 5 * ++b , и только в последнюю очередь выполняется сложение a + 5 * ++b

Скобки позволяют переопределить порядок вычислений:

Int a = 8; int b = 7; int c = (a + 5) * ++b; System.out.println(c); // 104

Несмотря на то, что операция сложения имеет меньший приоритет, но вначале будет выполняться именно сложение, а не умножение, так как операция сложения заключена в скобки.

Ассоциативность операций

Кроме приоритета операции отличаются таким понятием как ассоциативность . Когда операции имеют один и тот же приоритет, порядок вычисления определяется ассоциативностью операторов. В зависимости от ассоциативности есть два типа операторов:

    Левоассоциативные операторы, которые выполняются слева направо

    Правоассоциативные операторы, которые выполняются справа налево

Так, некоторые операции, например, операции умножения и деления, имеют один и тот же приоритет. Какой же тогда будет результат в выражении:

Int x = 10 / 5 * 2;

Стоит нам трактовать это выражение как (10 / 5) * 2 или как 10 / (5 * 2) ? Ведь в зависимости от трактовки мы получим разные результаты.

Поскольку все арифметические операторы (кроме префиксного инкремента и декремента) являются левоассоциативными, то есть выполняются слева направо. Поэтому выражение 10 / 5 * 2 необходимо трактовать как (10 / 5) * 2 , то есть результатом будет 4.

Операции с числами с плавающей точкой

Следует отметить, что числа с плавающей точкой не подходят для финансовых и других вычислений, где ошибки при округлении могут быть критичными. Например:

Double d = 2.0 - 1.1; System.out.println(d);

В данном случае переменная d будет равна не 0.9, как можно было бы изначально предположить, а 0.8999999999999999. Подобные ошибки точности возникают из-за того, что на низком уровне для представления чисел с плавающей точкой применяется двоичная система, однако для числа 0.1 не существует двоичного представления, также как и для других дробных значений. Поэтому если в таких случаях обычно применяется класс BigDecimal, который позволяет обойти подобные сиуации.

Большинство операций над примитивными типами выполняется не с помощью методов, а с помощью специальных символов, называемых знаком операции .

Операция присваивания

Присвоение переменной значения константы, другой переменной или выражения (переменных и/или констант, разделенных знаками операций), называется операцией присваивания и обозначается знаком "= ", например: x = 3 ; y = x; z = x; В Java допустимо многократное использование операции присваивания в одном выражении, например: x1 = x2 = x3 = 0 ; Эта операция выполняется справа налево, т.е. сначала переменной x3 присваивается значение 0 , затем переменной x2 присваивается значение переменной x3 (0), и, наконец, переменной x1 присваивается значение переменной x2 (0). Знаки операций, аргументами которых являются числа, разделяются на две категории: унарные (unary) знаки операций с одним аргументом и бинарные (binary) с двумя аргументами.

Унарные операции

В Java определены следующие унарные операции:
  • унарный минус " - " – меняет знак числа или выражения на противоположный;
  • унарный плюс " + " – не выполняет никаких действий над числом или выражением;
  • побитовое дополнение " ~ " (только для целых) – инвертирует все биты поля числа (меняет 0 на 1 и 1 на 0);
  • инкремент " ++ " (только для целых) – увеличивает значение переменной на 1;
  • декремент " -- " (только для целых) – уменьшает значение переменной на 1.
Примеры унарных операций " + " и " - ": int i = 3 , j, k; j= - i; // j = -3 k = + i; // k = 3 Пример операции побитового дополнения: int a = 15 ; int b; b = ~ a; // b = -16 Числа a и b являются числами типа int , т.е. представляются внутри компьютера как двоичные целые числа со знаком длиной 32 бита, поэтому двоичное представление чисел a и b будет выглядеть следующим образом: a = 00000000 00000000 00000000 00001111 b = 11111111 11111111 11111111 11110000 Как видно из этого представления, все нулевые биты в числе a изменены на единичные биты в числе b , а единичные биты в a изменены на нулевые биты. Десятичным представлением числа b будет –16 . Знаки операции инкремента и декремента могут размещаться как до, так и после переменной. Эти варианты называются соответственно префиксной и постфиксной записью этих операции. Знак операции в префиксной записи возвращает значение своего операнда после вычисления выражения. При постфиксной записи знак операции сначала воз­вращает значение своего операнда и только после этого вычисляет инкремент или декремент, например: int x = 1 , y, z; y = ++ x; z= x++ ; Переменная y будет присвоено значение 2 , поскольку сначала значение x будет увеличено на 1 , а затем результат будет присвоен переменной y . Переменной z будет присвоено значение 1 , поскольку сначала переменной z будет присвоено значение, а затем значение x будет увеличено на 1 . В обоих случаях новое значение переменной x будет равно 2 . Следует отметить, что в Java, в отличие от языка C, операции декремента и инкремента могут применяться и к вещественным переменным (типа float и double). Бинарные знаки операций подразделяются на операции с числовым результатом и операции сравнения, результатом которых является булевское значение.

Арифметические бинарные операции

В Java определены следующие арифметические бинарные операции :
  • сложение " + ";
  • вычитание " - ";
  • умножение " * ";
  • деление " / ";
  • вычисление остатка от деления целых чисел " % " (возвращает остаток от деления первого числа на второе, причем результат будет иметь тот же знак, что и делимое), например, результат операции 5%3 будет равен 2 , а результат операции (-7)%(-4) будет равен -3 . В Java операция может использоваться и для вещественных переменных (типа float или double).
Примеры бинарных арифметических операций: int x = 7 , x1, x2, x3, x4, x5; x1 = x + 10 ; // x1 = 17 x2 = x – 8 ; // x2 = -1 x3 = x2 * x; // x3 = -7 x4 = x/ 4 ; // x4 = 1 (при делении целых чисел // дробная часть отбрасывается) x5 = x% 4 // x5 = 3 (остаток от деления // 7 на 4)

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

  • Побитовые операции рассматривают исходные числовые значения как поля битов и выполняют над ними следующие действия:
  • установка бита в i -ой позиции поля результата в 1 , если оба бита в i -ых позициях операндов равны 1 , или в 0 в противном случае – побитовое И (" & ");
  • установка бита в i -ой позиции поля результата в 1 , если хотя бы один бит в i -ых позициях операндов равен 1 , или в 0 в противном случае – побитовое ИЛИ (" | ");
  • установка бита в i -ой позиции поля результата в 1 , если биты в i -ых позициях операндов не равны друг другу, или в 0 в противном случае – побитовое исключающее ИЛИ (" ^ ");
  • сдвиг влево битов поля первого операнда на количество битов, определяемое вторым операндом (бит знака числа при этом не меняется) – побитовый сдвиг влево с учетом знака " << ";
  • сдвиг вправо битов поля первого операнда на количество битов, определяемое вторым операндом (бит знака числа при этом не меняется) – побитовый сдвиг вправо с учетом знака " >> ";
  • сдвиг вправо битов поля первого операнда на количество битов, определяемое вторым операндом (бит знака числа при этом также сдвигается) – побитовый сдвиг вправо без учета знака " >>> ".
Примеры побитовых операций:
  1. Побитовое И

    int x = 112 ; int y = 94 ; int z; z = x & y; // z=80: 00000000 00000000 00000000 01010000
  2. Побитовое ИЛИ

    int x = 112 ; // x: 00000000 00000000 00000000 01110000 int y = 94 ; // y: 00000000 00000000 00000000 01011110 int z; z = x | y; // z = 126: 00000000 00000000 00000000 01111110
  3. Побитовое исключающее ИЛИ

    int x = 112 ; // x: 00000000 00000000 00000000 01110000 int y = 94 ; // y: 00000000 00000000 00000000 01011110 int z; z = x ^ y; // z = 46: 00000000 00000000 00000000 00101110
  4. Сдвиг влево с учетом знака

    int x = 31 , z; // x: 00000000 00000000 00000000 00011111 z = x << 2 ; // z = 124: 00000000 00000000 00000000 01111100
  5. Сдвиг вправо с учетом знака

    int x = - 17 , z; z = x >> 2 ; // z = -5: 11111111 11111111 11111111 11111011
  6. Сдвиг вправо без учета знака

    int x = - 17 , z; // x: 11111111 11111111 11111111 11101111 z = x >>> 2 ; // z = 1073741819 // z: 00111111 11111111 11111111 11111011

Комбинированные операции

В Java для бинарных арифметических операций можно использовать комбинированные (составные) знаки операций: идентификатор операция = выражение Это эквивалентно следующей операции: идентификатор = идентификатор операция выражение Примеры:
  1. Выражение x += b означает x = x + b .
  2. Выражение x -= b означает x = x - b .
  3. Выражение x *= b означает x = x * b .
  4. Выражение x /= b означает x = x / b .
  5. Выражение x %= b означает x = x % b .
  6. Выражение x &= b означает x = x & b .
  7. Выражение x |= b означает x = x | b .
  8. Выражение x ^= b означает x = x ^ b .
  9. Выражение x <<= b означает x = x << b .
  10. Выражение x >>= b означает x = x >> b .
  11. Выражение x >>>= b означает x = x >>> b .

Операции сравнения

В Java определены следующие операции сравнения:
  • " == " (равно), " != " (не равно),
  • " > " (больше), " >= " (больше или равно),
  • " < " (меньше) " <= " (меньше или равно)
имеют два операнда и возвращают булевское значение, соответствующее результату сравнения (false или true ). Следует обратить внимание, что при сравнении двух величин на равенство в Java, как и в C и в C++, используются символы "== " (два идущих без пробела друг за другом знака равенства), в отличие от оператора присваивания, в котором используется символ "= ". Использование символа " = " при сравнении двух величин либо вызывает ошибку при компиляции, либо приводит к неверному результату. Примеры операций сравнения: boolean isEqual, isNonEqual, isGreater, isGreaterOrEqual, isLess, isLessOrEqual; int x1 = 5 , x2 = 5 , x3 = 3 , x4 = 7 ; isEqual = x1 == x2; // isEqual = true isNonEqual = x1 != x2; // isNonEqual = false isGreater = x1 > x3; // isGreater = true // isGreaterOrEqual = true isGreaterOrEqual = x2 >= x3; isLess = x3 < x1; // isLess = true isLessOrEqual = x1 <= x3; // isLessOrEqual = false

Булевские операции

Булевские операции выполняются над булевскими переменными и их результатом также является значение типа boolean . В Java определены следующие булевские операции:
  • отрицание "!" – замена false на true , или наоборот;
  • операция И "&" – результат равен true , только, если оба операнда равны true , иначе результат – false ;
  • операция ИЛИ " | " – результат равен true , только, если хотя бы один из операндов равен true , иначе результат – false .
  • операция исключающее ИЛИ " ^ " – результат равен true , только, если операнды не равны друг другу, иначе результат – false .
Операции " & ", " | " и " ^ " можно, также как и соответствующие побитовые операции использовать в составных операциях присваивания: " &= ", " |= " и " ^= " Кроме того, к булевским операндам применимы операции " == " (равно) и " != " (не равно). Как видно из определения операций ИЛИ и И, операция ИЛИ приводит к результату true , когда первый операнд равен true , незави­симо от значения второго операнда, а операция И приводит к результату false , когда первый операнд равен false , независимо от значения второго операнда. В Java определены еще две булевские операции: вторые версии булевских операций И и ИЛИ, известные как укороченные (short-circuit) логические операции: укороченное И " && " и укороченное ИЛИ " || ". При использовании этих операций второй операнд вообще не будет вычисляться, что полезно в тех случаях, когда правильное функционирование правого операнда зависит от того, имеет ли левый операнд значение true или false . Примеры булевских операций: boolean isInRange, isValid, isNotValid, isEqual, isNotEqual; int x = 8 ; isInRange = x > 0 && x < 5 ; // isInRange = false isValid = x > 0 || x > 5 ; // isValid = true isNotValid = ! isValid; // isNotValid = false isEqual = isInRange == isValid; // isEqual = false // isNotEqual = true isNotEqual = isInRange != isValid

Условная операция

Условная операция записывается в форме выражение-1?выражение-2:выражение-3 . При этом сначала вычисляется выражение выражение-1 , которое должно дать булевское значение, а затем, если выражение-1 имеет значение true , вычисляется и возвращается выражение-2 как результат выполнения операции, либо (если выражение-1 имеет значение false), вычисляется и, как результат выполнения операции, возвращается выражение-3 . Пример условной операции: x= n> 1 ? 0 : 1 ; Переменной x будет присвоено значение 0 , если n>1 (выражение n>1 имеет значение true) или 1 , если n≤1 (выражение n>1 имеет значение false).

Старшинство операций

Операции в выражениях выполняются слева направо, однако, в соответствии со своим приоритетом. Так операции умножения в выражении y = x + z* 5 ; будет выполнена раньше, чем операция сложения, поскольку приоритет операции умножения выше, чем приоритет операции сложения. Приоритеты операций (в порядке уменьшения приоритета) в Java приведены в табл. 1.
Круглые скобки повышают старшинство операций, которые находятся внутри них. Так, если в приведенное выше выражение вставить скобки: y = (x + z) * 5 ; то сначала будет выполнена операция сложения, а затем операция умножения. Иногда скобки используют просто для того, чтобы сделать выражение более читаемым, например: (x > 1 ) && (x <= 5 ) ;

Преобразование и приведение типов при выполнении операций

В операции присваивания и арифметических выражениях могут использоваться литералы, переменные и выражения разных типов, например: double y; byte x; y = x + 5 ; В этом примере выполняется операция сложения переменной x типа byte и литерала 5 (типа int) и результат присваивается переменной y типа double . В Java, как и в языке C, преобразования типов при вычислении выражений могут выполняться автоматически, либо с помощью оператора приведения типа. Однако правила приведения типов несколько отличаются от правил языка C, и в целом являются более строгими, чем в языке C. При выполнении операции присваивания преобразование типов происходит автоматически, если происходит расширяющее преобразование (widening conversion) и два типа совместимы . Расширяющими преобразованиями являются преобразования byte ®short ®int ®long ®float ®double . Для расширяющих преобразований числовые типы, включая целый и с пла­вающей точкой, являются совместимыми друг с другом. Однако числовые типы не совместимы с типами char и boolean . Типы char и boolean не совмес­тимы также и друг с другом. В языке Java выполняется автоматическое преобразование типов также и при сохранении литеральной целочисленной константы (которая имеет по умолчанию тип int) в перемен­ных типа byte , short или long (однако если литерал имеет значение вне диапазона допустимых значений для данного типа, выдается сообщение об ошибке: возможная потеря точности). Если преобразование является сужающим (narrowing conversion), т. е. выполняется преобразование byte ¬ short ¬ char ¬ int ¬ long ¬ float ¬ double , то такое преобразование может привести к потере точности числа или к его искажению. Поэтому при сужающих преобразованиях при компиляции программы выводится диагностическое сообщение о несовместимости типов и файлы классов не создаются. Такое сообщение будет выдано и при попытке преобразование выражений типа byte или short в переменную типа char . Если все же необходимо выполнить такие преобразования, используется операция приведения (cast) типа, которая имеет следующий формат: (тип-преобразования ) значение , где тип-преобразования определяет тип, в который необходимо преобразовать заданное значение , например, в результате выполнения операторов: byte x = 71 ; char symbol = (char ) x; переменная symbol получит значение " G ". Если значение с плавающей точкой присваивается целому типу, то (если значение с плавающей точкой имеет дробную часть) при явном преобразовании типа происходит также усечение (truncation) числа. Так, в результате выполнения оператора int x = (int ) 77.85 ; переменная x получит значение 77 . Если же присваиваемое значение лежит вне диапазона типа-преобразования , то результатом преобразования будет остаток от деления значения на модуль диапазона присваиваемого типа (для чисел типа byte модуль диапазона будет равен 256 , для short – 65536 , для int – 4294967296 и для long – 18446744073709551616). Например, в результате выполнения оператора byte x = (byte ) 514 ; переменная x получит значение 2 . При преобразовании целых или вещественных чисел в данные типа char , преобразование в символ происходит, если исходное число лежит в диапазоне от 0 до 127, иначе символ получает значение " ? ". При выполнении арифметических и побитовых преобразований все значения byte и short , а также char расширяются до int , (при этом в вычислениях для char используется числовое значение кода символа) затем, если хотя бы один операнд имеет тип long , тип целого выражения расширяется до long . Если один из операндов имеет тип float , то тип полного вы­ражения расширяется до float , а если один из операндов имеет тип double , то тип результата будет double . Так, если объявлены переменные byte a, c; short b; то в выражении a + b* c – 15 L + 1.5F + 1.08 - 10 ; сначала, перед вычислением a + b*c значения переменных будут расширены до int , затем, поскольку константа 15 имеет тип long , перед вычитанием результат вычисления будет увеличен до long . После этого, поскольку литерал 1.5 имеет тип float перед сложением с этим литералом результат вычисления a + b*c – 15L будет расширен до float . Перед выполнением сложения с числом 1.08 результат предыдущих вычислений будет расширен до double (поскольку вещественные константы по умолчанию имеют тип double) и, наконец, перед выполнением последнего сложения литерал 10 (по умолчанию int) будет расширен до double . Таким образом, результат вычисления выражения будет иметь тип double . Автоматические расширения типов (особенно расширения short и byte до int) могут вызывать плохо распознаваемые ошибки во время компиляции. Например, в операторах: byte x = 30 , y = 5 ; x = x + y; перед выполнением сложения значение переменных x и y будет расширено до int , а затем при выполнении попытки присвоения результата вычисления типа int переменной типа byte будет выдано сообщение об ошибке. Чтобы этого избежать надо использовать во втором операторе явное преобразование типов: x = (byte ) (x + y) ; Выражение x + y необходимо заключит в скобки потому, что приоритет операции приведения типа, заключенной в скобки, выше, чем приоритет операции сложения. Кстати, если записать второй оператор в виде: x += y; то сообщения об ошибке не будет. Ссылка на перво


Top