Целочисленные типы - это byte, short, int, long, также к ним относят и char. Первые четыре типа имеют длину 1, 2, 4 и 8 байт соответственно, длина char — 2 байта, это непосредственно следует из того, что все символы Java описываются стандартом Unicode. Длины типов приведены только для оценки областей значения. Как уже говорилось, память в Java представляется виртуальной, и вычислить, сколько физических ресурсов займет та или иная переменная, так прямолинейно не получится.
4 основных типа являются знаковыми, char добавлен к целочисленным типам данных, так как с точки зрения JVM символ и его код - понятия взаимооднозначные. Конечно, код символа всегда положительный, поэтому char — единственный без знаковый тип. Инициализировать его можно как символьным, так и целочисленным литералом. Во всем остальном char - полноценный числовой тип данных, который может участвовать, например, в арифметических действиях, операциях сравнения и т.п. В таблице 4.1 сведены данные по всем разобранным типам:
Тбл. 4.1. Целочисленные типы данных.
Обратите внимание, что int вмещает примерно 2 миллиарда, а потому подходит во многих случаях, когда не требуются сверхбольшие числа. Чтобы представить себе размеры типа long, укажем, что именно он используется в Java для отсчета времени. Как и во многих языках, время отсчитывается от
1 января 1970 года в миллисекундах. Так вот, вместимость long позволяет отсчитывать время на протяжении миллионов веков(!), причем как в будущее, так и в прошлое.
Почему были вьделены именно эти два типа, int и long? Дело в том, что целочисленные литералы имеют тип int по умолчанию, или тип long, если стоит буква L или I. Именно поэтому корректным литералом считается только такое число, которое укладывается в 4 или 8 байт, соответственно. Иначе компилятор сочтет это ошибкой. Таким образом, следующие литералы являются корректными:
-2147483648
2147483648L
0L
111111111111111111L
Над целочисленными аргументами можно производить следующие операции:
• операции сравнения (возвращают булевское значение)
• числовые операции (возвращают числовое значение)
• унарные операции + и •
• арифметические операции +,-,*,/,%
• операции инкремента и декремента (в префиксной и постфиксной форме): ++ и --
• операции битового сдвига <<, », >»
• битовые операции ^, &, |, '^
• оператор с условием ?:
• оператор приведения типов
• оператор конкатенации со строкой +
Операторы сравнения вполне очевидны и отдельно мы их рассматривать не будем. Их результат всегда булевского типа (true или false).
Работа числовых операторов также понятна, к тому же пояснялась в предьдущей лекции. Единственное уточнение можно сделать относительно операторов + и -, которые могут быть как бинарными (иметь два операнда), так и унарными (иметь один операнд). Бинарные операнды являются операторами сложения и вычитания, соответственно. Унарный оператор + возвращает значение, равное аргументу (+х всегда равно х), Унарный оператор -, примененный к значению х, возвращает результат, равный 0-х. Неожиданный эффект имеет место в том случае, если аргумент равен наименьшему возможному значению примитивного типа.int х=-2147483648; // наименьшее возможное значение типа int inty=-x;
Теперь значение переменной у на самом деле равно не 2147483648, поскольку такое число не укладывается в область значений типа int, а в точности равно значению х! Другими словами, в этом примере выражение –х=х истинно!
Дело в том, что если при выполнении числовых операций над целыми числами возникает переполнение и результат не может быть сохранен в данном примитивном типе, то Java не создает никаких ошибок. Вместо этого все старшие биты, которые превышают вместимость типа, просто отбрасываются. Это может привести не только к потере точной абсолютной величины результата, но даже к искажению его знака, если на месте знакового бита окажется противоположное значение.
int х= 300000; print(x*x);
Результатом такого примера будет: -194313216
Возвращаясь к инвертированию числа -2147483648, мы видим, что математический результат равен в точности +2^\ или, в двоичном формате, 10....О (единица и 31 ноль). Но тип intрассматривает первую единицу как знаковый бит, и результат получается равным -2147483648.
Таким образом, явное выписывание в коде литералов, которые слишком велики для используемых типов, приводит к ошибке компиляции (см. лекцию 3). Если же переполнение возникает в результате выполнения операции, "лишние" биты просто отбрасываются.
Подчеркнем, что выражение типа -5 не является целочисленным литералом. На самом деле оно состоит из литерала 5 и оператора -. Напомним, что некоторые литералы (например, 2147483648) могут встречаться только в сочетании с унарным оператором -.
Кроме того, числовые операции в Java обладают еще одной особенностью. Хотя целочисленные типы имеют длину 8, 16, 32 и 64 бита, вычисления проводятся только с 32-х и 64-х битной точностью. А это значит, что перед вычислениями может потребоваться преобразовать тип одного или нескольких операндов.
Если хотя бы один аргумент операции имеет тип long, то все аргументы приводятся к этому типу и результат операции также будет типа long. Вычисление будет произведено с точностью в б4 бита, а более старшие биты, если таковые появляются в результате, отбрасываются.
Если же аргументов типа long нет, то вычисление производится с точностью в 32 бита, и все аргументы преобразуются в int (это относится к byte, short, char). Результат также имеет тип int. Все биты старше 32-го игнорируются.
Никакого способа узнать, произошло ли переполнение, нет. Расширим рассмотренный пример:
inti=300000;
print(i*i); // умножение с точностью 32 бита
long m=i;
print(m*m); // умножение с точностью 64 бита
print(1/(m-i)); // попробуем получить разность значений int и long
Результатом такого примера будет:
П94313216 90000000000
затем мы получим ошибку деления на ноль, поскольку переменные 1 и m хоть и разных типов, но хранят одинаковое математическое значение и их разность равна нулю. Первое умножение производилось с точностью в 32 бита, более старшие биты были отброшены. Второе — с точностью в 64 бита, ответ не исказился.
Вопрос приведения типов, и в том числе специальный оператор для такого действия, подробно рассматривается в следующих лекциях. Однако здесь хотелось бы отметить несколько примеров, которые не столь очевидны и могут создать проблемы при написании программ. Во-первых, подчеркнем, что результатом операции с целочисленными аргументами всегда является целое число. А значит, в следующем примере
double х= 1/2;
Переменной х будет присвоено значение 0, а не 0.5, как можно было бы ожидать. Подробно операции с дробными аргументами рассматриваются ниже, но чтобы получить значение 0.5, достаточно написать 1 ./2 (теперь первый аргумент дробный и результат не будет округлен).
Как уже упоминалось, время в Java измеряется в миллисекундах. Попробуем вычислить, сколько миллисекунд содержится в неделе и в месяце:
print( 1000*60*60*24*7); // вычисление для недели print( 1000*60*60*24*30); // вычисление для месяца
(следует перемножить количество миллисекунд в одной секунде (1000), секунд - в минуте (60), минут - в часе (60), часов - в дне (24) дней - в неделе и месяце (7 и 30, соответственно). Получаем:
604800000 -1702967296
Очевидно, во втором вычислении произошло переполнение Достаточно сделать последний аргумент величиной типа long:
print(1000*60*60*24*30L); // вычисление для месяца
Получаем правильный результат:
Подобные вычисления разумно переводить на 64-битную точность не на последней операции, а заранее, чтобы избежать переполнения
значений больший спектр значении, а потому Java не позволяет присвоить переменной меньшего
// пример вызовет ошибку компиляции
intx=1;
byte b=x;
А вот менее очевидный пример:
// пример вызовет ошибку компиляции
byteb=1;
bytec=b+1;
И здесь компилятор не сможет успешно завершить работу. При операции сложения значение переменной b будет преобразовано в ХИ и таким же будет результат сложения, а значит, его нельзя так просто присвоить переменной типа byte.
Аналогично:
// пример вызовет ошибку компиляции int х=2; long у=3; int z=x+y;
Здесь результат сложения будет уже типа long. Точно так же некорректна такая инициализация:
// пример вызовет ошибку компиляции byte b=5; byte с=-5;
Даже унарный оператор "-" проводит вычисления с точностью не меньше 32 бит.
Хотя во всех случаях инициализация переменных приводилась только для примера, а предметом рассмотрения были числовые операции, укажем корректный способ преобразовать тип числового значения:
byteb=1;
byte c=(byte)-b;
Итак, все числовые операторы возвращают результат типа intили long.Однако существует два исключения.
Первое из них — операторы инкрементации и декрементации. Их действие заключается в прибавлении или вычитании единицы из значения переменной, после чего результат сохраняется в этой переменной и значение всей операции равно значению переменной (до или после изменения, в зависимости от того, является оператор префиксным или постфиксным). А значит, и тип значения совпадает с типом переменной. (На самом деле, вычисления все равно производятся с точностью минимум 32 бита, однако при присвоении переменной результата его тип понижается.)
byte х=5;
byte у1=х++; // на момент начала исполнения х равен 5
byte у2=х—; // на момент начала исполнения х равен 6
byte уЗ=++х; // на момент начала исполнения х равен 5
byte у4=--х; // на момент начала исполнения х равен б
print(yl);
print(y2);
print(y3);
print(y4);
в результате получаем:
5 6 6 5
Никаких проблем с присвоением результата операторов ++ и — переменным типа byte. Завершая рассмотрение этих операторов, приведем еще один пример:
byte х=-128; print(-x):
byte у= 127; print(++y);
Результатом будет:
128 -128
Этот пример иллюстрирует вопросы преобразования типов при вычислениях и случаи переполнения.
Вторым исключением является оператор с условием ? :. Если второй и третий операнды имеют одинаковый тип, то и результат операции будет такого же типа.
byte х=2;
byte у=3;
byte z=(x>y) ? X : у; // верно, х и у одинакового типа
byte abs=(x>0) ? х : -х; // неверно!
Последняя строка неверна, так как третий аргумент содержит числовую операцию, стало быть, его тип int, а значит, и тип всей операции будет int, и присвоение некорректно. Даже если второй аргумент имеет тип byte, а третий — short, значение будет типа Int.
Наконец, рассмотрим оператор конкатенации со строкой. Оператор + может принимать в качестве аргумента строковые величины. Если одним из аргументов является строка, а вторым — целое число, то число будет преобразовано в текст и строки объединятся.