Опубликован: 04.12.2009 | Доступ: свободный | Студентов: 8424 / 660 | Оценка: 4.30 / 3.87 | Длительность: 27:27:00
Лекция 3:

Примитивные типы данных и операторы для работы с ними

< Лекция 2 || Лекция 3: 1234 || Лекция 4 >

3.5. Правила явного и автоматического преобразования типа при работе с числовыми величинами

Компьютер может проводить на аппаратном уровне целочисленные математические вычисления только с величинами типа int или long. При этом операнды в основных математических операциях ( + , - , * , / , % ) должны быть одного типа. Поэтому в тех случаях, когда операнды имеют разные типы, либо же типы byte, short или char, действуют правила автоматического преобразования типов: для величин типа byte, short или char сначала происходит преобразование в тип int, после чего производится их подстановка в качестве операндов. Если же один из операндов имеет тип long, действия производятся с числами типа long, поскольку второй операнд автоматически преобразуется к этому типу.

Аналогично, при работе с вещественными величинами в Java возможна работа на аппаратном уровне только с операндами типов float и double. При этом операнды в основных математических операциях должны быть одного типа. Если один из операндов имеет тип double, а другой float, действия производятся с числами типа double, поскольку операнд типа float автоматически преобразуется к типу double.

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

Рассмотрим теперь правила совместимости типов по присваиванию. Они просты: диапазон значений типа левой части не должен быть уже, чем диапазон типа правой. Поэтому в присваиваниях, где тип в правой части не умещается в диапазон левой части, требуется указывать явное преобразование типа. Иначе компилятор выдаст сообщение об ошибке с не очень адекватной диагностикой "possible loss of precision" ("возможная потеря точности").

Для явного преобразования типа в круглых скобках перед преобразуемой величиной ставят имя того типа, к которому надо преобразовать. Например, пусть имеется

double d=1.5;

Величину d можно преобразовать к типу float таким образом:

(float)d

Аналогично, если имеется величина f типа float, ее можно преобразовать в величину типа double таким образом:

(double)f

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

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

double d=1.5;
float f=(float)d;

Другие примеры допустимых присваиваний:

Таблица 3.10.
byte byte0=1; //-128..127
short short0=1;//- 32768.. 32767
char char0=1;//0.. 65535
int int0=1; //- 2.147483648E9.. 2.147483647E9
long long0=1;//-9.223E18.. 9.223E18
float float0=1;// ±(1.4E-45..3.402E38)
double double0=1;// ±(4.9E-324..1.797E308 )
Таблица 3.11.
short0=byte0;
byte0=( byte )short0;
char0=( char )short0;
int0=short0;
int0=char0;
char0=( char )int0;
short0=( short )int0;
long0=byte0;
byte0=( byte )long0;
long0=char0;
long0=int0;
int0=( int )long0;
float0=byte0;
float0=int0;
float0=( float )double0;
double0=float0;

3.6. Оболочечные классы. Упаковка (boxing) и распаковка (unboxing)

В ряде случаев вместо значения примитивного типа требуется объект. Например, для работы со списками объектов. Это связано с тем, что работа с объектами в Java может быть унифицирована, поскольку все классы Java являются наследниками класса Object, а для примитивных типов этого сделать нельзя.

Для таких целей в Java каждому примитивному типу сопоставляется объектный тип, то есть класс. Такие классы называются оболочечными ( class wrappers ). В общем случае они имеют те же имена, что и примитивные типы, но начинающиеся не со строчной, а с заглавной буквы. Исключение почему-то составляют типы int и char, для которых имена оболочечных классов Integer и Character.

Таблица 3.12.
Примитивный тип Оболочечный класс
byte Byte
short Short
char Character
int Integer
long Long
float Float
double Double

Внимание! Класс Character несколько отличается от остальных оболочечных числовых классов потому, что тип char "не вполне числовой".

Основное назначение оболочечных классов – создание объектов, являющихся оболочками над значениями примитивных типов. Процесс создания такого объекта ("коробки" - box) из значения примитивного типа называется упаковкой ( boxing ), а обратное преобразование из объекта в величину примитивного типа – распаковкой ( unboxing ). Оболочечные объекты ("обертки" для значения примитивного типа) хранят это значение в поле соответствующего примитивного типа, доступном по чтению с помощью функции имяТипаValue(). Например, метода byteValue() для объекта типа Byte. Но во многих случаях можно вообще не обращать внимания на отличие переменных с типом оболочечных классов от переменных примитивных типов, так как упаковка и распаковка при подстановке такого объекта в выражение происходит автоматически, и объект оболочечного типа в этих случаях внешне ведет себя как число. Таким образом, если нам необходимо хранить в объекте числовое значение, следует создать объект соответствующего оболочечного типа.

Например, возможны такие фрагменты кода:

Integer obj1=10;
int i1= obj1*2;
Byte b=1;
obj1=i1/10;
b=2;

Но не следует забывать, что при операциях упаковки-распаковки происходят внешне невидимые дополнительные операции копирования значений в промежуточные буферные ячейки, поэтому соответствующие вычисления несколько медленнее операций с примитивными типами и требуют несколько больше памяти. Поэтому в критических к быстродействию и занимаемым ресурсам местам программы их использование нежелательно. С другой стороны, автоматическая упаковка-распаковка (она появилась в пятой версии JDK) во многих случаях заметно упрощает программирование и делает текст программы более читаемым. Так что для участков программы, некритичных к быстродействию, ею вполне можно пользоваться.

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

  • Byte.MIN_VALUE, Byte.MAX_VALUE, Float.MIN_VALUE, Float.MAX_VALUE, Double.MIN_VALUE, Double.MAX_VALUE и т.п.

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

  • Byte.parseByte ( строка )
  • Short.parseShort ( строка )
  • Integer.parseInt ( строка )
  • Long.parseLong ( строка )
  • Float.parseFloat ( строка )
  • Double.parseDouble ( строка )

Они преобразуют строку в число соответствующего типа. Вызовы

  • Byte.valueOf ( строка )
  • Short.valueOf ( строка )
  • Integer.valueOf ( строка )
  • Long.valueOf ( строка )
  • Float.valueOf ( строка )
  • Double.valueOf ( строка )

аналогичны им, но возвращают не числовые значения, а объекты соответствующих оболочечных типов.

Примеры использования оболочечных классов:

  • int n1=Integer.MAX_VALUE;
  • double d1= Double.MIN_VALUE;

Отметим, что присваивание

  • double d2= Double.parseDouble(jTextField1.getText());

будет работать совершенно так же, как

  • double d2= Double.valueOf(jTextField1.getText());

несмотря на то, что во втором случае методом valueOf создается объект оболочечного типа Double. Поскольку в левой части присваивания стоит переменная типа double, происходит автоматическая распаковка, и переменной d2 присваивается распакованное значение. Сам объект при этом становится мусором – программная связь с ним теряется, и он через некоторое время удаляется из памяти системой сборки мусора. В данном случае ни быстродействие, ни объем памяти некритичны, поскольку операции взаимодействия с пользователем по компьютерным меркам очень медленные, а один объект оболочечного типа занимает пренебрежимо мало места в памяти (около сотни байт). Так что с потерями ресурсов в этом случае можно не считаться, обращая внимание только на читаемость текста программы. Поэтому автор предпочитает второй вариант присваивания: хотя он и "неоптимальный" по затрате ресурсов, но более читаем.

3.7. Приоритет операторов

При вычислении выражений важен приоритет операторов. Для операторов сложения, вычитания, умножения и деления он "естественный": умножение и деление обладают одинаковым наиболее высоким приоритетом, а сложение и вычитание – одинаковым приоритетом, который ниже. Таким образом, например,

  • a*b/c+d

это то же, что

  • ( (a*b)/c )+d

Круглые скобки позволяют группировать элементы выражений, при этом выражение в скобках вычисляется до того, как участвует в вычислении остальной части выражения. То есть скобки обеспечивают больший приоритет, чем все остальные операторы. Поэтому (a+b)*c будет вычисляться так: сначала вычислится сумма a+b, после чего полученный результат будет умножен на значение c.

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

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

Таблица 3.13.
Приоритет Группа операторов Операторы
1 высший Постфиксные ( ) [ ] .
2 Унарные ++ операнд

операнд ++

--операнд

операнд--

~ ! + операнд

- операнд

3 Создания объектов и преобразования типа new (тип) операнд
4 Мультипликативные * / %
5 Аддитивные + -
6 Сдвиги битов >> >>> <<
7 Отношения > >= < <= instanceof
8 Эквивалентности == !=
9 Побитовое И &
10 Побитовое исключающее ИЛИ ^
11 Побитовое ИЛИ |
12 Логическое И &&
13 Логическое ИЛИ ||
14 Условный ? :
15 низший Присваивания = Оператор = ( +=, -=, *=, /= и т.п. )
< Лекция 2 || Лекция 3: 1234 || Лекция 4 >
Полетаев Дмитрий
Полетаев Дмитрий
Не очень понятно про оболочечные Данные,ячейки памяти могут наверно размер менять,какое это значение те же операции только ячейки больше,по скорости тоже самое
Максим Старостин
Максим Старостин

Код с перемещением фигур не стирает старую фигуру, а просто рисует новую в новом месте. Точку, круг.