В ряде случаев вместо значения примитивного типа требуется объект. Например, для работы со списками объектов. Это связано с тем, что работа с объектами в Java может быть унифицирована, поскольку все классы Java являются наследниками класса Object, а для примитивных типов этого сделать нельзя.
Для таких целей в Java каждому примитивному типу сопоставляется объектный тип, то есть класс. Такие классы называются оболочечными (class wrappers). В общем случае они имеют те же имена, что и примитивные типы, но начинающиеся не со строчной, а с заглавной буквы. Исключение почему-то составляют типы int и char, для которых имена оболочечных классов Integer и Character.
Примитивный тип | Оболочечный класс |
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 присваивается распакованное значение. Сам объект при этом становится мусором – программная связь с ним теряется, и он через некоторое время удаляется из памяти системой сборки мусора. В данном случае ни быстродействие, ни объём памяти некритичны, поскольку операции взаимодействия с пользователем по компьютерным меркам очень медленные, а один объект оболочечного типа занимает пренебрежимо мало места в памяти (около сотни байт). Так что с потерями ресурсов в этом случае можно не считаться, обращая внимание только на читаемость текста программы. Поэтому автор предпочитает второй вариант присваивания: хотя он и “неоптимальный” по затрате ресурсов, но более читаем.
Приоритет операторов
При вычислении выражений важен приоритет операторов. Для операторов сложения, вычитания, умножения и деления он “естественный”: умножение и деление обладают одинаковым наиболее высоким приоритетом, а сложение и вычитание – одинаковым приоритетом, который ниже. Таким образом, например,
a*b/c+d
это то же, что
((a*b)/c)+d
Круглые скобки позволяют группировать элементы выражений, при этом выражение в скобках вычисляется до того, как участвует в вычислении остальной части выражения. То есть скобки обеспечивают больший приоритет, чем все остальные операторы. Поэтому
(a+b)*c будет вычисляться так: сначала вычислится сумма a+b, после чего полученный результат будет умножен на значение c.
Кроме перечисленных в Java имеется большое количество других правил, определяющих приоритеты различных операторов. Автор считает их изучение не только нецелесообразным, но даже вредным: программу следует писать так, чтобы все последовательности действий были очевидны и не могли вызвать сложностей в понимании текста программы и привести к логической ошибке. Поэтому следует расставлять скобки даже в тех случаях, когда они теоретически не нужны, но делают очевидной последовательность действий. Отметим, такие действия часто помогают заодно решить гораздо более сложные проблемы, связанные с арифметическим переполнением.
Далее в справочных целях приведена таблица приоритета операторов. Ей имеет смысл пользоваться в случае анализа плохо написанных программ, когда из текста программы неясна последовательность операторов.
Приори-тет | Группа операторов | Операторы | ||||
высший | Постфиксные | () | [ ] | . | ||
Унарные | ++ операнд операнд++ | --операнд операнд-- | ~ | ! | + операнд - операнд | |
Создания объектов и преобразования типа | new | (тип) операнд | ||||
Мультипликатив-ные | * | / | % | |||
Аддитивные | + | - | ||||
Сдвиги битов | >> | >>> | << | |||
Отношения | > | >= | < | <= | instanceof | |
Эквивалентности | == | != | ||||
Побитовое И | & | |||||
Побитовое исключающее ИЛИ | ^ | |||||
Побитовое ИЛИ | | | |||||
Логическое И | && | |||||
Логическое ИЛИ | || | |||||
Условный | ?: | |||||
низший | Присваивания | = | Оператор =(+=, -=, *=, /=и т.п.) |
Типы-перечисления (enum)
Иногда требуется использовать элементы, которые не являются ни числами, ни строками, но ведут себя как имена элементов и одновременно обладают порядковыми номерами. Например, названия месяцев или дней недели. В этих случаях используют перечисления. Для задания типа какого-либо перечисления следует написать зарезервированное слово enum (сокращение от enumeration – “перечисление”), после которого имя задаваемого типа, а затем в фигурных скобках через запятую элементы перечисления. В качестве элементов можно использовать любые простые идентификаторы (не содержащие квалификаторов вида имя1.имя2).
Тип-перечисление обязан быть глобальным – он может задаваться либо на уровне пакета, либо в каком-либо классе. Но его нельзя задавать внутри какого-либо метода. Элементы перечисления могут иметь любые имена, в том числе совпадающие в разных перечислениях или совпадающие с именами классов или их членов – каждое перечисление имеет своё собственное пространство имён. Доступ к элементу перечисления осуществляется с квалификацией именем типа-перечисления:
ИмяТипа.имяЭлемента
У каждого элемента перечисления имеется порядковый номер, соответствующий его положению в наборе - нумерация начинается с нуля. Поэтому первый элемент имеет номер 0, второй элемент – номер 1, и так далее. Имеется функция ordinal(), возвращающая порядковый номер элемета в перечислении. Также имеется функция compareTo, позволяющая сравнивать два элемента перечисления - она возвращает разницу в их порядковых номерах.
Строковое представление значения можно получить с помощью функции name(). Преобразование из строки в значение типа “перечисление” осуществляется с помощью функции класса valueOf, в которую передаётся строковое представление значения.
Если требуется рассматривать элементы перечисления как массив, можно воспользоваться функцией values() – она возвращает массив элементов, к которым можно обращаться по индексу. Формат вызова функции такой: ИмяТипа.values()
Для примера зададим типы-перечисления Monthes (“месяцы”) и Spring (“весна”), соответствующие различным наборам месяцев:
enum Monthes {jan,feb,mar,apr,may,jun,jul,aug,sept,oct,nov,dec};
enum Spring { march, apr, may };
Названия месяцев мы намеренно пишем со строчной буквы для того, чтобы было понятно, что это идентификаторы переменных, а не типы. А имя марта написано по-разному в типах Monthes и Spring для того, чтобы показать независимость их пространств имён.
Объявление переменных типа “перечисление” делается так же, как для всех остальных типов, при этом переменные могут быть как неинициализированы, так и инициализированы при задании:
public Monthes m1,m2=Monthes.mar, m3;
- при задании в классе общедоступных полей m1, m2 и m3,
Spring spr1=Spring.apr, spr2;
- при задании в методе локальной переменной или задании в классе поля spr1 с пакетным уровнем доступа.
После чего возможны следующие операторы:
spr2=spr1;
spr1=Spring.may;
System.out.println("Результат сравнения="+spr2.compareTo(Spring.march));
После выполнения этих операторов в консольное окно будет выведен текст
Результат сравнения=1,
поскольку в переменной spr2 окажется значение Spring.apr, порядковый номер которого на 1 больше, чем у значения Spring.march, с которым идёт сравнение.
Пусть в переменной spr2 хранится значение Spring.may. Порядковый номер значения, хранящегося в переменной, можно получить с помощью вызова spr2.ordinal(). Он возвратит число 2, так как may – третий элемент перечисления (сдвиг на 1 получается из-за того, что нумерация начинается с нуля).
Строковое представление значения, хранящегося в переменной spr2, можно получить с помощью вызова spr2.name(). Он возвратит строку “may” - имя типа в возвращаемое значение не входит.
Если переменная типа “перечисление” не инициализирована, в ней хранится значение null. Поэтому вызов
System.out.println("spr2="+spr2);
осуществлённый до присваивания переменной spr2 значения возвратит строку
spr2=null
А вот попытки вызовов spr2.ordinal() или spr2.name() приведут к возникновению ошибки (исключительной ситуации) с диагностикой
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
Получение значения типа Spring по номеру, хранящемуся в переменной i, осуществляется так:
spr1=Spring.values()[i];
Преобразование из строки в значение типа Spring будет выглядеть так:
spr1=Spring.valueOf("march");
Краткие итоги по главе 3
ü Величины типа boolean принимают значения true или false.
ü Логические операторы && -“И”, || - “ИЛИ”, ^ - “Исключающее ИЛИ”,! – “НЕ” применимы к величинам булевского типа. Логические выражения в Java вычисляются в соответствии с укороченным оцениванием.
ü Операторы сравнения применимы к любым величинам a и b одного типа, а также к произвольным числовым величинам a и b, не обязательно имеющим один тип. В качестве оператора сравнения на равенство используется составной символ, состоящий из двух подряд идущих символа равенства “==”.
ü В Java имеются встроенные примитивные целые типы byte, short, int, long и символьный тип char, в некотором смысле также являющийся целочисленным. При этом только тип char беззнаковый, все остальные – знаковые.
ü Для задания в тексте программы численных литерных констант типа long, выходящих за пределы диапазона чисел типа int, после написания числа следует ставить постфикс – букву L.
ü Константами называются именованные ячейки памяти с неизменяемым содержимым. Объявление констант осуществляется в классе, при этом перед именем типа константы ставится комбинация зарезервированных слов public и final.
ü В Java имеется два встроенных примитивных вещественных типа float и double (точнее, типы чисел в формате с плавающей точкой).
ü Математические функции, а также константы “пи” (Math.PI) и “е” (Math.E) заданы в классе Math, находящемся в пакете java.lang.
ü Целочисленные математические вычисления проводятся на аппаратном уровне только с величинами типа int или long. Для величин типа byte, short или char сначала происходит преобразование в тип int, после чего производится их подстановка в качестве операндов. Если же один из операндов имеет тип long, действия производятся с числами типа long, поскольку второй операнд автоматически преобразуется к этому типу.
ü При работе с вещественными величинами в Java возможна работа на аппаратном уровне только с операндами типов float и double. Если один из операндов имеет тип double, а другой float, действия производятся с числами типа double, поскольку операнд типа float автоматически преобразуется к типу double.
ü Если один из операндов целочисленный, а другой вещественный, сначала идёт преобразование целочисленного операнда к такому же вещественному типу, а потом выполняется оператор.
ü В Java каждому примитивному типу сопоставляется объектный тип, то есть класс. Такие классы называются оболочечными (class wrappers). В общем случае они имеют те же имена, что и примитивные типы, но начинающиеся не со строчной, а с заглавной буквы. Исключение составляют типы int и char, для которых имена оболочечных классов Integer и Character.
ü Основное назначение оболочечных классов – создание объектов, являющихся оболочками над значениями примитивных типов. Процесс создание такого объекта (“коробки” - box) из значения примитивного типа называется упаковкой (boxing), а обратное преобразование из объекта в величину примитивного типа – распаковкой (unboxing). Упаковка и распаковка для числовых классов осуществляется автоматически.
ü В оболочечных классах имеется ряд полезных методов и констант. Например, минимальное по модулю не равное нулю и максимальное значение числового типа можно получить с помощью констант, вызываемых через имя оболочечного типа: Integer.MIN_VALUE, Integer.MAX_VALUE, Float.MIN_VALUE, Float.MAX_VALUE, Double.MIN_VALUE, Double.MAX_VALUE. и т.п.
ü В Java имеется 15 уровней приоритета операторов. В хорошо написанной программе ставятся скобки, повышающие читаемость программы, даже если они не нужны с точки зрения таблицы приоритетов.
ü Иногда требуется использовать элементы, которые не являются ни числами, ни строками, но ведут себя как имена элементов и одновременно обладают порядковыми номерами. Например, названия месяцев или дней недели. В этих случаях используют перечисления.
Типичные ошибки:
- Очень часто встречается ошибка, когда вместо сравнения численных значений вида a==b программист пишет a=b.Чаще всего такая ошибка возникает в условных операторах: вместо if(a==b){...} пишут if(a=b){...}. В Java такая ошибка диагностируется на этапе компиляции для всех типов, кроме булевского. Для булевских a и b вызов if(a=b){...} приведёт к тому, что величине a будет присвоено значение b, и результат присваивания возвратится равным этому значению. А диагностики ошибки, к сожалению, выдано не будет.
- Начинающие программисты, изучавшие ранее язык BASIC, пытаются использовать выражение вида a ^ b для возведения в степень, то есть для вычисления выражения ab. В Java для такой операции следует использовать выражение Math.pow(a,b).
- Программисты, изучавшие ранее C/C++, пытаются использовать логические выражения вида a & b или a | b для полной (не укороченной) оценки логических выражений. В Java нет полной (не укороченной) оценки логических выражений, а операторы & и | зарезервированы для арифметических побитовых операций “И” и “ИЛИ”.
- При использовании оператора instanceof пытаются написать instanceOf.
- Путают класс java.lang.Math и пакет java.math.
- Очень часто встречается ошибка, когда забывают, что для величин типа byte, short или char сначала происходит преобразование в тип int, и только после этого производится их подстановка в качестве операндов. Поэтому, в частности, побитовые операции с величинами этих типов дают те же результаты, что и при работе с величинами типа int.
- Не ставятся скобки, группирующие операнды в длинных выражениях, где стороннему программисту неочевидна последовательность выполнения операндов.
- Элементы перечисления пытаются задавать как строковые, или же пытаются предварительно задать числовые переменные с именами элементов перечисления.
Задания
- На основе проекта с графическим пользовательским интерфейсом создать новый проект. В нём для каждого из целочисленных и вещественных типов задать переменные и кнопки с соответствующими надписями. При нажатии на кнопки должны показываться диалоговые панели с сообщением об имени и значении соответствующей переменной.
- На основе проекта с графическим пользовательским интерфейсом создать новый проект. В нём сделать два пункта ввода, метку и кнопки “Сложить”, “Умножить”, “Разделить”, “sin”. По нажатию на кнопки“Сложить”, “Умножить”, “Разделить” в метку должен выводиться результат. Действия проводить с величинами типа double. По нажатию на кнопку “sin” в метку должен выводиться синус значения, показывавшегося до того в метке.
- На основе проекта с графическим пользовательским интерфейсом создать новый проект. В нём сделать пункта ввода и радиогруппу с выбором варианта для каждого из целочисленных типов, а также кнопку JButton с надписью “Преобразовать в число”. При выборе соответствующего варианта в пункте ввода должно возникать случайным образом генерируемое число, лежащее в пределах допустимых значений для этого типа. При нажатии на кнопку содержимое пункта ввода должно быть преобразовано в число соответствующего типа, и это значение должно быть показано с помощью диалоговой панели с сообщением.
Работа с выбором вариантов осуществляется следующим образом:
if(jRadioButton1.isSelected())
оператор1;
if(jRadioButton2.isSelected())
оператор2;
if(jRadioButton3.isSelected())
оператор3;
- Создать приложение Java с графическим пользовательским интерфейсом. В нём должны быть перечислением Spring (“весна”), в котором перечислены весенние месяцы, и кнопки “ m2=m1” и “Вывести значение m2”. Задать две переменные типа Spring – m1 и m2. Переменную m1 инициализированной, со значением April, переменную m2 – не инициализированной. При нажатии на кнопку “ m2=m1” переменной m2 должно присваиваться значение m1. При нажатии на кнопку “Вывести значение m2” должно выводиться значение переменной m2.