Примитивные типы в Java
Идеальный объектно-ориентированный язык
Как уже говорилось ранее, Java – объектно-ориентированный язык. В Java есть классы (class), и есть объекты (object) – экземпляры заданного класса. Однако, Java не является идеальным объектно-ориентированным языком. Почему?
Давайте рассмотрим, как должен выглядеть идеальный объектно-ориентированный язык. Если бы Java была таковым, то все типы в ней были бы классами, все переменные – объектами, все действия с переменными – функциями. Например, тип целых чисел был бы неким классом, а все действия с целыми числами – методами этого класса. Описать это пришлось бы следующим образом:
public class Int { public void increment() { ... } public void add(Int n) { ... } ... }
И, если мы хотим, к примеру, увеличить значение переменной n
на 1
, то вместо
привычного
int n = 0; n++;
необходимо было бы писать так:
Int n = new Int(); n.increment();
Таким образом, в идеальном объектно-ориентированном языке не должно быть никаких плюсов и прочих операций – их необходимо описывать как методы класса. Не должно быть даже присваивания. И, соответственно, не должно быть никаких типов кроме классов. И вообще, в таком случае при создании объекта (например, «целого числа»), создавалась бы некоторая «коробочка», в которую помещалось бы наше некоторое значение, и каждая операция требовала бы «залезания» в эту «коробочку», поиска в ней нашего числа и уже потом произведения с ним определенного действия. Это занимало бы кучу времени. Но Java не является идеальным объектно-ориентированным языком, поэтому мы избежим всех этих неудобств.
Примитивные типы в Java
В Java существует 8 типов, кроме классов:
boolean |
логический тип |
char |
символьный тип |
byte |
целочисленные типы |
short |
|
int |
|
long |
|
float |
типы с плавающей точкой |
double |
Рассмотрим их подробнее.
boolean
Это логический тип. Переменные этого типа могут принимать только два значения:
true
– истина иfalse
– ложь. Памяти на переменную такого типа требуется 1 бит.С переменными этого типа можно производить следующие операции:
операция обозначение присваивание a = true;
отрицание !b
and
(логическое “и”)a && b
a & b
or
(логическое “или”)a || b
a | b
xor
(исключающее “или”)a ^ b
Как видно из таблицы, у операций
and
иor
по два вида обозначения. В чем разница? Рассмотрим логическое выражение:a && b
оно будет вычисляться следующим образом. Если
а
– истинно, то проверяется условиеb
, еслиа
ложно, тоb
не проверяется, а значением выражения автоматически становится ложь. Если же мы напишем:a & b
то будут проверяться в любом случае оба выражения. Аналогичная ситуация с операцией «или».
char
Символьный тип данных. Переменная такого типа занимает 2 байта памяти, т.к. хранится в кодировке
unicode
.С переменными этого типа можно производить только операции присваивания, но зато различными способами. Самый простой из них выглядит так:
c = 'b';
Символ можно представить также в виде его кода, записанного в восьмиричной системе счисления:
c = '\077';
Где
077
– это обязательно трехзначное число, не большее чем377
(=255
в десятичной системе счисления)Или же в 16-ричной системе счисления следующим образом:
c = '\u12a4';
Кроме того, существуют специальные символы, такие как знак абзаца, разрыв страницы и др. Знак абзаца запишется, например, так:
c = '\n';
Не будем перечислять их здесь. При необходимости всегда можно заглянуть в справочник.
byte, short, int, long
Целочисленные типы. Они отличаются только максимальной длиной и, соответственно, размером занимаемой памяти.
тип размер диапазон значений byte
1 байт -27… 27-1 short
2 байта -215…215-1 int
4 байта -231…231-1 long
8 байт -263…263-1 Для переменных этих типов определены следующие арифметические операции:
операция обозначение сложение a + b
вычитание a - b
умножение a * b
целочисленное деление a / b
взятие остатка a % b
инкремент a++
++a
декремент a--
--a
Увеличить a
наb
a += b
Увеличить a
вb
разa *= b
Уменьшить a
наb
a -= b
Уменьшить a
вb
разa /= b
Как видно из таблицы, у операций инкремента и декремента по два вида обозначения. В чем разница? Если мы напишем:
a = b++;
то вначале переменной
а
будет присвоено значение переменнойb
, а затем значение переменнойb
будет увеличено на единицу. Если же мы напишем:a = ++b;
то сначала
b
увеличится на единицу, а затем это новое (увеличенное) значение будет присвоено переменнойа
. Аналогичная ситуация с операцией декремента.Кроме того, для переменных типа int и long определены побитовые логические операции:
операция обозначение and
(побитовое “и”)a & b
or
(побитовое “или”)a | b
xor
(побитовое исключающее “или”)a ^ b
побитовое отрицание
~a
сдвиг на 1 бит влево a << 1
сдвиг на 1 бит вправо a >> 1
a >>> 1
Побитовые логические операции могут также быть применены к переменным типа byte и short, но в этом случае переменная такого типа будет неявно приведена к типу int, и только после этого к ней будет применена логическая операция.
После побитового сдвига влево, в конце всегда появляется 0. А вот на побитовый сдвиг вправо следует обратить внимание. Как в памяти хранятся числа? Например, число типа int, равное 231-1 запишется в виде:
011..1
(ноль и тридцать одна единица)Нолик в начале означает, что число положительное. Если же мы хотим записать отрицательное число, то в начале будет стоять единица. Поэтому, когда мы выполняем побитовый сдвиг вправо, то, если число было положительным, вначале остается 0, если же отрицательным, то вначале появляется 1.. Но если по какой-то причине мы все же хотим, чтобы вначале в любом случае появлялся 0, то побитовый сдвиг вправо надо выполнить следующим образом:
a >>> b;
Для сравнения можно привести следующие примеры:
231-1 >> 1 = 230-1 -1 >> 1 = -1 231-1 >>> 1 = 230-1 -1 >>> 1 = 231-1
Если мы хотим указать, что константа типа именно long, то на конце следует поставить l (или L). Т.е. следует писать так:
10000000000l
1L
Рекомендуется всегда писать L, так как l легко спутать с 1. Такая путаница может стать причиной ошибки, а также ухудшает читаемость кода.
float, double
Типы с плавающей точкой. Переменные такого типа занимают в памяти 4 и 8 байт соответственно. С ними можно производить следующие операции:
операция обозначение сложение a + b
вычитание a - b
умножение a * b
деление a / b
Кроме того, в переменных такого типа можно хранить не только числа. У них есть еще три стандартных значения:
Описание Выдаваемое значение Специальная константа в Java +
∞infinity
Float.POSITIVE_INFINITY
Double.POSITIVE_INFINITY
-
∞-infinity
Float.NEGATIVE_INFINITY
Double.NEGATIVE_INFINITY
неопределенность (Not a Number) NaN
Float.NaN
Double.NaN
Таким образом, если при делении на 0 в целых числах произойдет ошибка, то при делении, например, положительного числа на 0 в вещественных, результатом будет +∞. При делении 0 на 0 –
NaN
.Для обозначения самого маленького и самого большого чисел тоже существуют специальные константы:
Float.MIN_VALUE; Float.MAX_VALUE; Double.MIN_VALUE; Double.MAX_VALUE;
Кроме того, как и у целочисленного типа
long
, если мы хотим указать, что константа типа именноfloat
, то на конце следует поставить букву f (или F):1.0f 0.005F
Операции сравнения
Для всех типов, кроме boolean
, определены операции сравнения:
операция | обозначение |
равенство | == |
неравенство | != |
больше | > |
меньше | < |
больше или равно | >= |
меньше или равно | <= |
Для типа boolean
можно выполнять проверку на равенство/неравенство.
Особенностью
типов float
и double
является значение NaN
. NaN
никогда не равно
самому себе и ничему другому. Для проверки, является ли значением переменной
(типа float/double
) NaN
, существуют
специальные функции:
float f; double d; .... Float.isNaN(f); Double.isNaN(d);
Преобразование типов
Иногда возникают ситуации, когда необходимо переменной одного типа присвоить значение переменной другого типа. Например:
int a = 1; byte b = 2; a = b;
Как поступать в этом случае? Все типы условно можно расположить «в порядке увеличения»:
byte < short
< int < long < float < double
Если мы хотим переменой «большего» типа присвоить значение «меньшего» типа, то выполняем обычное присваивание:
int a = 1; float f = 2.2f; f = a;
Если же наоборот, переменной «меньшего» типа значение «большего», то надо указывать тип, в который мы преобразовываем:
int a = 1; float f = 2.2f; a = (int)f;
При этом дробная часть просто отбросится.
Преобразовывать
число в переменную типа char
всегда необходимо явно, а
вот переменную типа char
явно преобразовывают только в byte
и short
:
int a; char c = '*', c1; byte b; a = c; c1 = (char)a; b = (byte)c1;
Небольшое отступление о кодировке Unicode
Как уже говорилось, переменные типа char в Java хранятся в кодировке Unicode. Еще недавно казалось, что любой символ помещается в 7 бит, т.е. для хранения одного символа достаточно 1 байта. Однако, в некоторых алфавитах символов больше, чем 256. Идея Unicode состоит в том, чтобы хранить 1 символ четырьмя байтами. Т.е. в такой кодировке помещается 232, что больше 4 млрд. символов.
Существуют
кодировки символов с постоянной длиной: UCS–2
(двухбайтовая кодировка) и UCS–4
(четырехбайтовая кодировка). В кодировке UCS–4
символ записывается, как есть, согласно стандарту Unicode.
На самом деле в таблице Unicode количество всех символов меньше 216, и для записи символа в кодировке UCS–2
,
берётся его запись, согласно стандарту Unicode, и из неё выкидываются первые два байта. Также существует кодировка UTF–8
,
в которой разные символы записываются разным количеством байт. Наиболее часто встречающиеся – 1 байтом, менее встречающиеся
– 2 байтами и совсем редко встречающиеся – 3 байтами.
Символ с номером, меньшим чем 128 записывается так:
0.......
|
Символ с номером большим, чем 128, но меньшим, чем 2048 – так:
110..... |
10...... |
Символ с номером между 2048 и 65536 – так:
1110.... |
10...... |
10...... |
В данный момент любой символ в UTF-8
записывается не
более чем 3 байтами.