Примитивные и ссылочные типы данных
Теперь на примере переменных можно проиллюстрировать различие между примитивными и ссылочными типами данных. Рассмотрим пример, когда объявляются две переменные одного типа, приравниваются друг другу, а затем значение одной из них изменяется. Что произойдет со второй переменной?
Возьмем простой тип int:
int a=5; // объявляем первую переменную и
// инициализируем ее
int b=a; // объявляем вторую переменную и
// приравниваем ее к первой
a=3; // меняем значение первой
print(b); // проверяем значение второй
Здесь и далее мы считаем, что функция print(...) позволяет нам некоторым (неважно, каким именно) способом узнать значение ее аргумента (как правило, для этого используют функцию из стандартной библиотеки System.out.println(...), которая выводит значение на системную консоль).
В результате мы увидим, что значение переменной b не изменилось, оно осталось равным 5. Это означает, что переменные простого типа хранят непосредственно свои значения и при приравнивании двух переменных происходит копирование данного значения. Чтобы еще раз подчеркнуть эту особенность, приведем еще один пример:
byte b=3;
int a=b;
В данном примере происходит преобразование типов (оно подробно рассматривается в соответствующей лекции). Для нас сейчас важно констатировать, что переменная b хранит значение 3 типа byte, а переменная a – значение 3 типа int. Это два разных значения, и во второй строке при присваивании произошло копирование.
Теперь рассмотрим ссылочный тип данных. Переменные таких типов всегда хранят ссылки на некоторые объекты. Рассмотрим для примера класс, описывающий точку на координатной плоскости с целочисленными координатами. Описание класса – это отдельная тема, но в нашем простейшем случае оно тривиально:
class Point {
int x, y;
}
Теперь составим пример, аналогичный приведенному выше для int -переменных, считая, что выражение new Point(3,5) создает новый объект-точку с координатами (3,5).
Point p1 = new Point(3,5);
Point p2=p1;
p1.x=7;
print(p2.x);
В третьей строке мы изменили горизонтальную координату точки, на которую ссылалась переменная p1, и теперь нас интересует, как это сказалось на точке, на которую ссылается переменная p2. Проведя такой эксперимент, можно убедиться, что в этот раз мы увидим обновленное значение. То есть объектные переменные после приравнивания остаются "связанными" друг с другом, изменения одной сказываются на другой.
Таким образом, примитивные переменные являются действительными хранилищами данных. Каждая переменная имеет значение, не зависящее от остальных. Ссылочные же переменные хранят лишь ссылки на объекты, причем различные переменные могут ссылаться на один и тот же объект, как это было в нашем примере. В этом случае их можно сравнить с наблюдателями, которые с разных позиций смотрят на один и тот же объект и одинаково видят все происходящие с ним изменения. Если же один наблюдатель сменит объект наблюдения, то он перестает видеть и изменения, происходящие с прежним объектом:
Point p1 = new Point(3,5);
Point p2=p1;
p1 = new Point(7,9);
print(p2.x);
В этом примере мы получим 3, то есть после третьей строки переменные p1 и p2 ссылаются на различные объекты и поэтому имеют разные значения.
Теперь легко понять смысл литерала null. Такое значение может принять переменная любого ссылочного типа. Это означает, что ее ссылка никуда не указывает, объект отсутствует. Соответственно, любая попытка обратиться к объекту через такую переменную (например, вызвать метод или взять значение поля) приведет к ошибке.
Также значение null можно передать в качестве любого объектного аргумента при вызове функций (хотя на практике многие методы считают такое значение некорректным).
Память в Java с точки зрения программиста представляется не нулями и единицами или набором байтов, а как некое виртуальное пространство, в котором существуют объекты. И доступ к памяти осуществляется не по физическому адресу или указателю, а лишь через ссылки на объекты. Ссылка возвращается при создании объекта и далее может быть сохранена в переменной, передана в качестве аргумента и т.д. Как уже говорилось, допускается наличие нескольких ссылок на один объект. Возможна и противоположная ситуация – когда на какой-то объект не существует ни одной ссылки. Такой объект уже недоступен программе и является "мусором", то есть без толку занимает аппаратные ресурсы. Для их освобождения не требуется никаких усилий. В состав любой виртуальной машины обязательно входит автоматический сборщик мусора garbage collector – фоновый процесс, который как раз и занимается уничтожением ненужных объектов.
Очень важно помнить, что объектная переменная, в отличие от примитивной, может иметь значение другого типа, не совпадающего с типом переменной. Например, если тип переменной – некий класс, то переменная может ссылаться на объект, порожденный от наследника этого класса. Все случаи подобного несовпадения будут рассмотрены в следующих разделах курса.
Теперь рассмотрим примитивные и ссылочные типы данных более подробно.
Примитивные типы
Как уже говорилось, существует 8 простых типов данных, которые делятся на целочисленные ( integer ), дробные ( floating-point ) и булевы ( boolean ).
Целочисленные типы
Целочисленные типы – это byte, short, int, long, также к ним относят и char . Первые четыре типа имеют длину 1, 2, 4 и 8 байт соответственно, длина char – 2 байта, это непосредственно следует из того, что все символы Java описываются стандартом Unicode. Длины типов приведены только для оценки областей значения. Как уже говорилось, память в Java представляется виртуальной и вычислить, сколько физических ресурсов займет та или иная переменная, так прямолинейно не получится.
4 основных типа являются знаковыми. char добавлен к целочисленным типам данных, так как с точки зрения JVM символ и его код – понятия взаимооднозначные. Конечно, код символа всегда положительный, поэтому char – единственный беззнаковый тип. Инициализировать его можно как символьным, так и целочисленным литералом. Во всем остальном char – полноценный числовой тип данных, который может участвовать, например, в арифметических действиях, операциях сравнения и т.п. В табл 4.1 сведены данные по всем разобранным типам:
Таблица 4.1. Целочисленные типы данных. | ||
Название типа | Длина (байты) | Область значений |
byte | -128 .. 127 | |
short | -32.768 .. 32.767 | |
int | -2.147.483.648 .. 2.147.483.647 | |
long | -9.223.372.036.854.775.808 .. 9.223.372.036.854.775.807 (примерно 1019) | |
char | '\u0000' .. '\uffff', или 0 .. 65.535 |
Обратите внимание, что int вмещает примерно 2 миллиарда, а потому подходит во многих случаях, когда не требуются сверхбольшие числа. Чтобы представить себе размеры типа long, укажем, что именно он используется в Java для отсчета времени. Как и во многих языках, время отсчитывается от 1 января 1970 года в миллисекундах. Так вот, вместимость long позволяет отсчитывать время на протяжении миллионов веков(!), причем как в будущее, так и в прошлое.
Почему были выделены именно эти два типа, int и long? Дело в том, что целочисленные литералы имеют тип int по умолчанию, или тип long, если стоит буква L или l. Именно поэтому корректным литералом считается только такое число, которое укладывается в 4 или 8 байт, соответственно. Иначе компилятор сочтет это ошибкой. Таким образом, следующие литералы являются корректными:
-2147483648
2147483648L
0L
111111111111111111L
Над целочисленными аргументами можно производить следующие операции:
- операции сравнения (возвращают булево значение)
- <, <=, >, >=
- ==, !=
- числовые операции (возвращают числовое значение)
- унарные операции + и -
- арифметические операции +, -, *, /, %
- операции инкремента и декремента (в префиксной и постфиксной форме): ++ и --
- операции битового сдвига <<, >>, >>>
- битовые операции ~, &, |, ^
- оператор с условием ?:
- оператор приведения типов
- оператор конкатенации со строкой +
Операторы сравнения вполне очевидны и отдельно мы их рассматривать не будем. Их результат всегда булева типа ( true или false ).
Работа числовых операторов также понятна, к тому же пояснялась в предыдущей лекции. Единственное уточнение можно сделать относительно операторов + и -, которые могут быть как бинарными (иметь два операнда), так и унарными (иметь один операнд). Бинарные операнды являются операторами сложения и вычитания, соответственно. Унарный оператор + возвращает значение, равное аргументу ( +x всегда равно x ). Унарный оператор -, примененный к значению x, возвращает результат, равный 0-x. Неожиданный эффект имеет место в том случае, если аргумент равен наименьшему возможному значению примитивного типа.
int x=-2147483648; // наименьшее возможное
// значение типа int
int y=-x;
Теперь значение переменной y на самом деле равно не 2147483648, поскольку такое число не укладывается в область значений типа int, а в точности равно значению x! Другими словами, в этом примере выражение -x==x истинно!
Дело в том, что если при выполнении числовых операций над целыми числами возникает переполнение и результат не может быть сохранен в данном примитивном типе, то Java не создает никаких ошибок. Вместо этого все старшие биты, которые превышают вместимость типа, просто отбрасываются. Это может привести не только к потере точной абсолютной величины результата, но даже к искажению его знака, если на месте знакового бита окажется противоположное значение.
int x= 300000;
print(x*x);
Результатом такого примера будет:
-194313216
Возвращаясь к инвертированию числа -2147483648, мы видим, что математический результат равен в точности +231, или, в двоичном формате, 1000 0000 0000 0000 0000 0000 0000 0000 (единица и 31 ноль). Но тип int рассматривает первую единицу как знаковый бит, и результат получается равным -2147483648.
Таким образом, явное выписывание в коде литералов, которые слишком велики для используемых типов, приводит к ошибке компиляции (см. лекцию 3). Если же переполнение возникает в результате выполнения операции, "лишние" биты просто отбрасываются.
Подчеркнем, что выражение типа -5 не является целочисленным литералом. На самом деле оно состоит из литерала 5 и оператора -. Напомним, что некоторые литералы (например, 2147483648 ) могут встречаться только в сочетании с унарным оператором -.
Кроме того, числовые операции в Java обладают еще одной особенностью. Хотя целочисленные типы имеют длину 8, 16, 32 и 64 бита, вычисления проводятся только с 32-х и 64-х битной точностью. А это значит, что перед вычислениями может потребоваться преобразовать тип одного или нескольких операндов.
Если хотя бы один аргумент операции имеет тип long, то все аргументы приводятся к этому типу и результат операции также будет типа long. Вычисление будет произведено с точностью в 64 бита, а более старшие биты, если таковые появляются в результате, отбрасываются.
Если же аргументов типа long нет, то вычисление производится с точностью в 32 бита, и все аргументы преобразуются в int (это относится к byte, short, char ). Результат также имеет тип int. Все биты старше 32-го игнорируются.
Никакого способа узнать, произошло ли переполнение, нет. Расширим рассмотренный пример:
int i=300000;
print(i*i); // умножение с точностью 32 бита
long m=i;
print(m*m); // умножение с точностью 64 бита
print(1/(m-i)); // попробуем получить разность
// значений int и long
Результатом такого примера будет:
-194313216
затем мы получим ошибку деления на ноль, поскольку переменные i и m хоть и разных типов, но хранят одинаковое математическое значение и их разность равна нулю. Первое умножение производилось с точностью в 32 бита, более старшие биты были отброшены. Второе – с точностью в 64 бита, ответ не исказился.
Вопрос приведения типов, и в том числе специальный оператор для такого действия, подробно рассматривается в следующих лекциях. Однако здесь хотелось бы отметить несколько примеров, которые не столь очевидны и могут создать проблемы при написании программ. Во-первых, подчеркнем, что результатом операции с целочисленными аргументами всегда является целое число. А значит, в следующем примере
double x = 1/2;
переменной x будет присвоено значение 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, соответственно). Получаем:
-1702967296
Очевидно, во втором вычислении произошло переполнение. Достаточно сделать последний аргумент величиной типа long:
print(1000*60*60*24*30L);
// вычисление для месяца
Получаем правильный результат:
Подобные вычисления разумно переводить на 64-битную точность не на последней операции, а заранее, чтобы избежать переполнения.
Понятно, что типы большей длины могут хранить больший спектр значений, а потому Java не позволяет присвоить переменной меньшего типа значение большего типа. Например, такие строки вызовут ошибку компиляции:
// пример вызовет ошибку компиляции
int x=1;
byte b=x;
Хотя для программиста и очевидно, что переменная b должна получить значение 1, что легко укладывается в тип byte, однако компилятор не может вычислять значение переменной x при обработке второй строки, он знает лишь, что ее тип – int.
А вот менее очевидный пример:
// пример вызовет ошибку компиляции
byte b=1;
byte c=b+1;
И здесь компилятор не сможет успешно завершить работу. При операции сложения значение переменной b будет преобразовано в тип int и таким же будет результат сложения, а значит, его нельзя так просто присвоить переменной типа byte.
Аналогично:
// пример вызовет ошибку компиляции
int x=2;
long y=3;
int z=x+y;
Здесь результат сложения будет уже типа long. Точно так же некорректна такая инициализация:
// пример вызовет ошибку компиляции
byte b=5;
byte c=-b;
Даже унарный оператор " - " проводит вычисления с точностью не меньше 32 бит.
Хотя во всех случаях инициализация переменных приводилась только для примера, а предметом рассмотрения были числовые операции, укажем корректный способ преобразовать тип числового значения:
byte b=1;
byte c=(byte)-b;
Итак, все числовые операторы возвращают результат типа int или long. Однако существует два исключения.
Первое из них – операторы инкрементации и декрементации. Их действие заключается в прибавлении или вычитании единицы из значения переменной, после чего результат сохраняется в этой переменной и значение всей операции равно значению переменной (до или после изменения, в зависимости от того, является оператор префиксным или постфиксным). А значит, и тип значения совпадает с типом переменной. (На самом деле, вычисления все равно производятся с точностью минимум 32 бита, однако при присвоении переменной результата его тип понижается.)
byte x=5;
byte y1=x++; // на момент начала исполнения x равен 5
byte y2=x--; // на момент начала исполнения x равен 6
byte y3=++x; // на момент начала исполнения x равен 5
byte y4=--x; // на момент начала исполнения x равен 6
print(y1);
print(y2);
print(y3);
print(y4);
В результате получаем:
Никаких проблем с присвоением результата операторов ++ и -- переменным типа byte. Завершая рассмотрение этих операторов, приведем еще один пример:
byte x=-128;
print(-x);
byte y=127;
print(++y);
Результатом будет:
-128
Этот пример иллюстрирует вопросы преобразования типов при вычислениях и случаи переполнения.
Вторым исключением является оператор с условием ?:. Если второй и третий операнды имеют одинаковый тип, то и результат операции будет такого же типа.
byte x=2;
byte y=3;
byte z=(x>y) ? x : y;
// верно, x и y одинакового типа
byte abs=(x>0) ? x : -x;
// неверно!
Последняя строка неверна, так как третий аргумент содержит числовую операцию, стало быть, его тип int, а значит, и тип всей операции будет int, и присвоение некорректно. Даже если второй аргумент имеет тип byte, а третий – short, значение будет типа int.
Наконец, рассмотрим оператор конкатенации со строкой. Оператор + может принимать в качестве аргумента строковые величины. Если одним из аргументов является строка, а вторым – целое число, то число будет преобразовано в текст и строки объединятся.
int x=1;
print("x="+x);
Результатом будет:
x=1
Обратите внимание на следующий пример:
print(1+2+"text");
print("text"+1+2);
Его результатом будет:
3text
text12
Отдельно рассмотрим работу с типом char. Значения этого типа могут полноценно участвовать в числовых операциях:
char c1=10;
char c2='A';
// латинская буква A (\u0041, код 65)
int i=c1+c2-'B';
Переменная i получит значение 9.
Рассмотрим следующий пример:
char c='A';
print(c);
print(c+1);
print("c="+c);
print('c'+'='+с);
Его результатом будет:
A
c=A
В первом случае в метод print было передано значение типа char, поэтому отобразился символ. Во втором случае был передан результат сложения, то есть число, и именно число появилось на экране. Далее при сложении со строкой тип char был преобразован в текст в виде символа. Наконец в последней строке произошло сложение трех чисел: 'c' (код 99), '=' (код 61) и переменной c (т.е. код 'A' - 65 ).
Для каждого примитивного типа существуют специальные вспомогательные классы-обертки (wrapper classes). Для типов byte, short, int, long, char это Byte, Short, Integer, Long, Character. Эти классы содержат многие полезные методы для работы с целочисленными значениями. Например, преобразование из текста в число. Кроме того, есть класс Math, который хоть и предназначен в основном для работы с дробными числами, но также предоставляет некоторые возможности и для целых.
В заключение подчеркнем, что единственные операции с целыми числами, при которых Java генерирует ошибки,– это деление на ноль (операторы / и % ).
Дробные типы
Дробные типы – это float и double . Их длина - 4 и 8 байт, соответственно. Оба типа знаковые. Ниже в таблице сведены их характеристики:
Таблица 4.2. Дробные типы данных. | ||
Название типа | Длина (байты) | Область значений |
float | 3.40282347e+38f ; 1.40239846e-45f | |
double | 1.79769313486231570e+308 ; 4.94065645841246544e-324 |
Для целочисленных типов область значений задавалась верхней и нижней границами, весьма близкими по модулю. Для дробных типов добавляется еще одно ограничение – насколько можно приблизиться к нулю, другими словами – каково наименьшее положительное ненулевое значение. Таким образом, нельзя задать литерал заведомо больший, чем позволяет соответствующий тип данных, это приведет к ошибке overflow. И нельзя задать литерал, значение которого по модулю слишком мало для данного типа, компилятор сгенерирует ошибку underflow.
// пример вызовет ошибку компиляции
float f = 1e40f;
// значение слишком велико, overflow
double d = 1e-350;
// значение слишком мало, underflow
Напомним, что если в конце литерала стоит буква F или f, то литерал рассматривается как значение типа float. По умолчанию дробный литерал имеет тип double, при желании это можно подчеркнуть буквой D или d.
Над дробными аргументами можно производить следующие операции:
- операции сравнения (возвращают булево значение)
- <, <=, >, >=
- ==, !=
- числовые операции (возвращают числовое значение)
- унарные операции + и -
- арифметические операции +, -, *, /, %
- операции инкремента и декремента (в префиксной и постфиксной форме): ++ и --
- оператор с условием ?:
- оператор приведения типов
- оператор конкатенации со строкой +
Практически все операторы действуют по тем же принципам, которые предусмотрены для целочисленных операторов (оператор деления с остатком % рассматривался в предыдущей лекции, а операторы ++ и -- также увеличивают или уменьшают значение переменной на единицу). Уточним лишь, что операторы сравнения корректно работают и в случае сравнения целочисленных значений с дробными. Таким образом, в основном необходимо рассмотреть вопросы переполнения и преобразования типов при вычислениях.
Для дробных вычислений появляется уже два типа переполнения – overflow и underflow. Тем не менее, Java и здесь никак не сообщает о возникновении подобных ситуаций. Нет ни ошибок, ни других способов обнаружить их. Более того, даже деление на ноль не приводит к некорректной ситуации. А значит, дробные вычисления вообще не порождают никаких ошибок.
Такая свобода связана с наличием специальных значений дробного типа. Они определяются спецификацией IEEE 754 и уже перечислялись в лекции 3:
- положительная и отрицательная бесконечности (positive/negative infinity);
- значение "не число", Not-a-Number, сокращенно NaN ;
- положительный и отрицательный нули.
Все эти значения представлены как для типа float, так и для double.
Положительную и отрицательную бесконечности можно получить следующим образом:
1f/0f // положительная бесконечность,
// тип float
-1d/0d // отрицательная бесконечность,
// тип double
Также в классах Float и Double определены константы POSITIVE_INFINITY и NEGATIVE_INFINITY. Как видно из примера, такие величины получаются при делении конечных величин на ноль.
Значение NaN можно получить, например, в результате следующих действий:
0.0/0.0 // деление ноль на ноль
(1.0/0.0)*0.0 // умножение бесконечности на ноль
Эта величина также представлена константами NaN в классах Float и Double.
Величины положительный и отрицательный ноль записываются очевидным образом:
0.0 // дробный литерал со значением
// положительного нуля
+0.0 // унарная операция +, ее значение -
// положительный ноль
-0.0 // унарная операция -, ее значение -
// отрицательный ноль
Все дробные значения строго упорядочены. Отрицательная бесконечность меньше любого другого дробного значения, положительная – больше. Значения +0.0 и -0.0 считаются равными, то есть выражение 0.0==-0.0 истинно, а 0.0>-0.0 – ложно. Однако другие операторы различают их, например, выражение 1.0/0.0 дает положительную бесконечность, а 1.0/-0.0 – отрицательную.
Единственное исключение - значение NaN. Если хотя бы один из аргументов операции сравнения равняется NaN, то результат заведомо будет false (для оператора != соответственно всегда true ). Таким образом, единственное значение x, при котором выражение x!=x истинно,– именно NaN.
Возвращаемся к вопросу переполнения в числовых операциях. Если получаемое значение слишком велико по модулю ( overflow ), то результатом будет бесконечность соответствующего знака.
print(1e20f*1e20f);
print(-1e200*1e200);
В результате получаем:
Infinity
-Infinity
Если результат, напротив, получается слишком мал ( underflow ), то он просто округляется до нуля. Так же поступают и в том случае, когда количество десятичных знаков превышает допустимое:
print(1e-40f/1e10f); // underflow для float
print(-1e-300/1e100); // underflow для double
float f=1e-6f;
print(f);
f+=0.002f;
print(f);
f+=3;
print(f);
f+=4000;
print(f);
Результатом будет:
0.0
-0.0
1.0E-6
0.002001
3.002001
4003.002
Как видно, в последней строке был утрачен 6-й разряд после десятичной точки.
Другой пример (из спецификации языка Java):
double d = 1e-305 * Math.PI;
print(d);
for (int i = 0; i < 4; i++)
print(d /= 100000);
Результатом будет:
3.141592653589793E-305
3.1415926535898E-310
3.141592653E-315
3.142E-320
0.0
Таким образом, как и для целочисленных значений, явное выписывание в коде литералов, которые слишком велики ( overflow ) или слишком малы ( underflow ) для используемых типов, приводит к ошибке компиляции (см. лекцию 3). Если же переполнение возникает в результате выполнения операции, то возвращается одно из специальных значений.
Теперь перейдем к преобразованию типов. Если хотя бы один аргумент имеет тип double, то значения всех аргументов приводятся к этому типу и результат операции также будет иметь тип double. Вычисление будет произведено с точностью в 64 бита.
Если же аргументов типа double нет, а хотя бы один аргумент имеет тип float, то все аргументы приводятся к float, вычисление производится с точностью в 32 бита и результат имеет тип float.
Эти утверждения верны и в случае, если один из аргументов целочисленный. Если хотя бы один из аргументов имеет значение NaN, то и результатом операции будет NaN.
Еще раз рассмотрим простой пример:
print(1/2);
print(1/2.);
Результатом будет:
0.5
Достаточно одного дробного аргумента, чтобы результат операции также имел дробный тип.
Более сложный пример:
int x=3;
int y=5;
print (x/y);
print((double)x/y);
print(1.0*x/y);
Результатом будет:
0.6
0.6
В первый раз оба аргумента были целыми, поэтому в результате получился ноль. Однако поскольку оба операнда представлены переменными, в этом примере нельзя просто поставить десятичную точку и таким образом перевести вычисления в дробный тип. Необходимо либо преобразовать один из аргументов (второй вывод на экран), либо вставить еще одну фиктивную операцию с дробным аргументом (последняя строка).
Приведение типов подробно рассматривается в другой лекции, однако обратим здесь внимание на несколько моментов.
Во-первых, при приведении дробных значений к целым типам дробная часть просто отбрасывается. Например, число 3.84 будет преобразовано в целое 3, а -3.84 превратится в -3. Для математического округления необходимо воспользоваться методом класса Math.round(…).
Во-вторых, при приведении значений int к типу float и при приведении значений типа long к типу float и double возможны потери точности, несмотря на то, что эти дробные типы вмещают гораздо большие числа, чем соответствующие целые. Рассмотрим следующий пример:
long l=111111111111L;
float f = l;
l = (long) f;
print(l);
Результатом будет:
Тип float не смог сохранить все значащие разряды, хотя преобразование от long к float произошло без специального оператора в отличие от обратного перехода.
Для каждого примитивного типа существуют специальные вспомогательные классы-обертки (wrapper classes). Для типов float и double это Float и Double. Эти классы содержат многие полезные методы для работы с дробными значениями. Например, преобразование из текста в число.
Кроме того, класс Math предоставляет большое количество методов для операций над дробными значениями, например, извлечение квадратного корня, возведение в любую степень, тригонометрические и другие. Также в этом классе определены константы PI и основание натурального логарифма E.
Булев тип
Булев тип представлен всего одним типом boolean, который может хранить всего два возможных значения – true и false . Величины именно этого типа получаются в результате операций сравнения.
Над булевыми аргументами можно производить следующие операции:
- операции сравнения (возвращают булево значение)
- ==, !=
- логические операции (возвращают булево значение)
- !
- &, |, ^
- &&, ||
- оператор с условием ?:
- оператор конкатенации со строкой +
Логические операторы && и || обсуждались в предыдущей лекции. В операторе с условием ?: первым аргументом может быть только значение типа boolean. Также допускается, чтобы второй и третий аргументы одновременно имели булев тип.
Операция конкатенации со строкой превращает булеву величину в текст "true" или "false" в зависимости от значения.
Только булевы выражения допускаются для управления потоком вычислений, например, в качестве критерия условного перехода if.
Никакое число не может быть интерпретировано как булево выражение. Если предполагается, что ненулевое значение эквивалентно истине (по правилам языка С), то необходимо записать x!=0. Ссылочные величины можно преобразовывать в boolean выражением ref!=null.
Ссылочные типы
Итак, выражение ссылочного типа имеет значение либо null, либо ссылку, указывающую на некоторый объект в виртуальной памяти JVM.
Дата добавления: 2016-03-22; просмотров: 913;