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

К арифметическим операциям относятся:

  • сложение + (плюс);
  • вычитание - (дефис);
  • умножение * (звездочка);
  • деление / (наклонная черта — слэш);
  • взятие остатка от деления (деление по модулю) % (процент);
  • инкремент (увеличение на единицу) ++;
  • декремент (уменьшение на единицу) --

Между сдвоенными плюсами и минусами нельзя оставлять пробелы. Сложение, вычитание и умножение целых значений выполняются как обычно, а вот деление целых значений в результате дает опять целое (так называемое "целое деление"), например, 5/2 даст в результате 2, а не 2.5, а 5/(-3) даст -1. Дробная часть попросту отбрасывается, происходит усечение частного. Это поначалу обескураживает, но потом оказывается удобным для усечения чисел.

Замечание

В Java принято целочисленное деление.

Это странное для математики правило естественно для программирования: если оба операнда имеют один и тот же тип, то и результат имеет тот же тип. Достаточно написать 5/2.0 или 5.0/2 или 5.0/2.0 и получим 2.5 как результат деления вещественных чисел.

Операция деление по модулю определяется так: а % b = а - (а / b) * b; например, 5%2 даст в результате 1, а 5% (-3) даст, 2, т.к. 5 = (-3) * (-1) + 2, но (-5)%3 даст -2, поскольку -5 = 3 * (-1) - 2.

Операции инкремент и декремент означают увеличение или уменьшение значения переменной на единицу и применяются только к переменным, но не к константам или выражениям, нельзя написать 5++ или (а + b)++.

Например, после приведенных выше описаний i++ даст -99, a j—- даст 99.

Интересно, что эти операции 'можно записать?и перед переменной: ++i, — j. Разница проявится только в выражениях: при первой формe записи (постфиксной) в выражении участвует старое значение переменной и только потом происходит увеличение или уменьшение ее значения. При второй форме записи (префиксной) сначала изменится переменная и ее новое значение будет участвовать в выражении.

Например, после приведенных выше описаний, (k++) + 5 даст в результате 10004, а переменная k примет значение 10000. Но в той же исходной ситуации (++k) + 5 даст 10005, а переменная k станет равной 10000.

Приведение типов

Результат арифметической операции имеет тип int, кроме того случая, когда один из операндов типа long. В этом случае результат будет типа long.

Перед выполнением арифметической операции всегда происходит повышение (promotion) типов byte, short, char. Они преобразуются в тип int, а может быть, и в тип long, если другой операнд типа long. Операнд типа int повышается до типа long, если другой операнд типа long. Конечно, числовое значение операнда при этом не меняется.

Это правило приводит иногда к неожиданным результатам. Попытка откомпилировать простую программу, представленную в листинге 1.3, приведет к сообщениям компилятора, показанным на рис. 1.3.

Листинг 1.3. Неверное определение переменной

class InvalidDef{

public static void main (String [] args) { 

byte b1 = 50, b2 = -99;

short k = b1 + b2; // Неверно! '

System.out.println("k=" + k);

}

}

Эти сообщения означают, что в файле InvalidDef.java, в строке 4, обнаружена возможная потеря точности (possible loss of precision). Затем приводятся обнаруженный (found) и нужный (required) типы, выводится строка, в которой обнаружена (а не сделана) ошибка, и отмечается символ, при разборе которого найдена ошибка. Затем указано общее количество обнаруженных (а не сделанных) ошибок (1 error).

Рис. 1.3. Сообщения  компилятора об ошибке

В таких случаях следует выполнить явное приведение типа. В данном случае это будет сужение (narrowing) типа int до типа short. Оно осуществляется операцией явного приведения, которая записывается перед приводимым значением в виде имени типа в скобках. Определение

short k = (short)(b1 + b2);

будет верным.

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

byte b = (byte) 300;

даст переменной b значение 44. Действительно, в двоичном представлении числа 300, равном 100101100, отбрасывается старший бит и получается 00101100.

Таким же образом можно произвести и явное расширение (widening) типа, если в этом есть необходимость. .

Если результат целой операции выходит за диапазон своего типа int или long, то автоматически происходит приведение по модулю, равному длине этого диапазона, и вычисления продолжаются, переполнение никак не отмечается.

Замечание

В языке Java нет целочисленного переполнения.


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

В языке Java шесть обычных операций сравнения целых чисел по величине: 

  • больше >; 
  • меньше <;
  • больше или равно >=; 
  • меньше или равно <=; 
  • равно ==; 
  • не равно !=.

Сдвоенные символы записываются без пробелов, их нельзя переставлять местами, запись => будет неверной.

Результат сравнения — логическое значение: true, в результате, например, сравнения 3 != 5; или false, например, в результате сравнения 3 == 5.

Для записи сложных сравнений следует привлекать логические.операции. Например, в вычислениях часто приходится делать проверки вида а < х < b. Подобная запись на языке Java приведет к сообщению об ошибке, поскольку первое сравнение, а < х, даст true или false, a Java не знает, больше это, чем b, или меньше. В данном случае следует написать выражение (а < х) && (х < b), причем здесь скобки можно опустить, написать просто а < х && х < b, но об этом немного позднее.

 

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

Иногда приходится изменять значения отдельных битов в целых данных. Это выполняется с помощью побитовых (bitwise) операций путем наложения маски. В языке Java есть четыре побитовые операции:

  • дополнение (complement) ~ (тильда); 
  • побитовая конъюнкция (bitwise AND) &; 
  • побитовая дизъюнкция (bitwise OR) |; 
  • побитовое исключающее ИЛИ (bitwise XOR) ^.

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

Таблица 1.3. Побитовые операции

nl

n2

~nl

nl & n2

nl | n2

nl ^ n2

1

0

0

0

1

0

0

0

0

В нашем примере b1 == 50, двоичное представление 00110010, b2 == -99, двоичное представление 10011101. Перед операцией происходит повышение до типа int. Получаем представления из 32-х разрядов для b1 — 0...00110010, для b2 — 1...l0011101. В результате побитовых операций получаем:

  • ~b2 == 98, двоичное представление 0...01100010;
  • b1 & b2 == 16, двоичное представление 0...00010000;
  • b1 | b2 == -65, двоичное представление 1...10111111;
  • b1 ^ b2 == -81, двоичное представление 1...10101111. 

Двоичное представление каждого результата занимает 32 бита. 

Заметьте, что дополнение ~х всегда эквивалентно (-x)-1.

Сдвиги

В языке Java есть три операции сдвига двоичных разрядов: 

  • сдвиг влево <<; 
  • сдвиг вправо >>; 
  • беззнаковый сдвиг вправо >>>.

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

Например, операция b1<< 2 сдвинет влево на 2 разряда предварительно повышенное значение 0...00110010 переменной b1, что даст в результате 0...011001000, десятичное 200. Освободившиеся справа разряды заполняются нулями, левые разряды, находящиеся за 32-м битом, теряются.

Операция b2 << 2 сдвинет повышенное значение 1...10011101 на два разряда влево. В результате получим 1...1001110100, десятичное значение —396.

Заметьте, что сдвиг влево на п разрядов эквивалентен умножению числа на 2 в степени n.

Операция b1 >> 2 даст в результате 0...00001100, десятичное 12, а b2 >> 2 — результат 1..11100111, десятичное -25, т. е. слева распространяется старший бит, правые биты теряются. Это так называемый арифметический сдвиг.

Операция беззнакового сдвига во всех случаях ставит слева на освободившиеся места нули, осуществляя логический сдвиг. Но вследствие предварительного повышения это имеет эффект только для нескольких старших разрядов отрицательных чисел. Так, b2 >>> 2 имеет результатом 001...100111, десятичное число 1 073 741 799.

Если же мы хотим получить логический сдвиг исходного значения loomoi переменной b2, т. е., 0...00100111, надо предварительно наложить на b2 маску, обнулив старшие биты: (b2 & 0XFF) >>> 2.

Замечание

Будьте осторожны при использовании сдвигов вправо.