Изучение новых функциональных возможностей, включенных в последнюю версию Java 2 5.0, начнем с так долго ожидаемых всеми программистами на языке Java автоупаковки (autoboxing) и автораспаковки (auto-unboxing). Выбор этот сделан по трем причинам. Во-первых, автоупаковка/автораспаковка сильно упрощает и рационализирует исходный код, в котором требуется объектное представление базовых типов языка Java, таких как int или char. Поскольку такие ситуации часто встречаются в текстах программ на Java, выигрыш от применения средств автоупаковки/распаковки получат практически все, программирующие на этом языке. Во-вторых, автоупаковка/автораспаковка во многом способствует простоте и удобству применения другого нового средства — настройки типов (generics). Следовательно, понимание автоупаковки и автораспаковки понадобится для последующего изучения этого механизма. В-третьих, автоупаковка/распаковка плавно изменяет наши представления о взаимосвязи объектов и данных базовых типов. Эти изменения гораздо глубже, чем может показаться на первый взгляд из-за концептуальной простоты двух описываемых здесь новых функциональных возможностей. Их влияние ощущается во всем языке Java.
Автоупаковка и автораспаковка напрямую связаны с оболочками типов (type wrapper) языка Java и со способом вставки значений в экземпляры таких оболочек и извлечения значений из них. По этой причине мы начнем с краткого обзора оболочек типов и процесса упаковки и распаковки значений для них.
Обзор оболочек типов и упаковки значений
Как вам известно, в языке Java используются базовые типы (также называемые простыми), такие как int или double, для хранения элементарных данных типов, поддерживаемых языком. Для хранения таких данных из-за более высокой производительности применяются базовые типы, а не объекты. В этом случае использование объектов добавляет неприемлемые издержки даже к простейшей вычислительной операции. Таким образом, базовые типы не являются частью иерархии объектов и не наследуют класс Object.
Несмотря на выигрыш в производительности, предлагаемый базовыми типами, возникают ситуации, требующие обязательного объектного представления. Например, Вы не можете передать в метод переменную базового типа как параметр по ссылке. Кроме того, много стандартных cтруктур данных, реализованных в языке Java, оперирует объектами, и, следовательно, Вы не можете использовать эти структуры для хранения данных базовых типов. Для подобных (и других) случаев Java предоставляет оболочки типов, представляющие собой классы, которые инкапсулируют данные простого типа в объект. Далее перечислены классы оболочки типов.
Boolean Byte Character Double
Float Long Integer Short
Значение базового типа инкапсулируется в оболочку в момент конструирования объекта. Упакованное таким образом значение можно получить обратно с помощью вызова одного из методов, определенных в оболочке. Например, все оболочки числовых типов предлагают следующие методы:
byte byteValue() double doubleValue() float floatValue()
int intValue() long longValue() short shortValue()
Каждый метод возвращает значение заданного базового типа. Например, объект типа Long может вернуть значение одного из встроенных числовых типов, включая short, double или long.
Процесс инкапсуляции значения в объект называется упаковкой (boxing). До появления Java 2 версии 5.0 вся упаковка выполнялась программистом вручную, с помощью создания экземпляра оболочки с нужным значением. В приведенной далее строке кода значение 100 упаковывается вручную в объект типа Integer:
Integer iOb = new Integer(l00);
В приведенном примере новый объект типа Integer со значением 100 создается явно и ссылка на него присваивается переменной iOb.
Процесс извлечения значения из оболочки типа называется распаковкой (unboxing). И снова, до появления Java 2 версии 5.0 вся распаковка выполнялась вручную с помощью вызова метода оболочки для получения значения из объекта.
В следующей строке кода значение из объекта iOb вручную распаковывается в переменную типа int:
int i = iOb.intValue();
В данном случае метод intValue() возвращает значение типа int из объекта iOb. Как объяснялось ранее, есть и другие методы, позволяющие извлечь из объекта значение другого числового типа, такого как byte, short, long, double или float. Например, для получения значения типа long из объекта iOb Вам следует вызвать метод iOb.longValue(). Таким образом, можно распаковать значение в переменную простого типа, отличающегося от типа оболочки.
Начиная с первоначальной версии языка Java, для упаковки и распаковки вручную выполнялась одна и та же базовая процедура, приведенная в предыдущих примерах. Хотя такой способ упаковки и распаковки работает, он утомителен и подвержен ошибкам, так как требует от программиста вручную создавать подходящий объект для упаковки значения и при необходимости его распаковки явно задавать переменную соответствующего базового типа. К счастью Java 2, v5.0 коренным образом модернизирует эти важнейшие процедуры, вводя средства автоупаковки/распаковки.
Основы автоупаковки/распаковки
Автоупаковка (autoboxing) — это процесс автоматической инкапсуляции данных простого типа, такого как int или double, в эквивалентную ему оболочку типа, как только понадобится объект этого типа. При этом нет необходимости в явном создании объекта нужного типа. Автораспаковка (auto-unboxing) — это процесс автоматического извлечения из упакованного объекта значения, когда оно потребуется. Вызовы методов, таких как intValue() и doubleValue(), становятся ненужными.
Добавление средств автоупаковки/автораспаковки значительно упрощает кодирование ряда алгоритмов, исключая утомительные упаковку и распаковку, выполняемые вручную. Кроме того, эти новые средства программирования позволяют избежать ошибок за счет устранения возможности распаковки вручную неверного типа из оболочки. Автоупаковка также облегчает использование настраиваемых типов (generics) и запоминание данных базовых типов в коллекциях.
Благодаря автоупаковке исчезает необходимость в создании вручную объекта для инкапсуляции значения простого типа. Вам нужно только присвоить это значение указателю на объект типа-оболочки. Язык Java автоматически создаст для вас этот объект. В следующей строке приведен пример современного способа конструирования объекта типа Integer, хранящего значение 100:
Integer iOb = 100; // автоматически упаковывает значение типа int
Обратите внимание на то, что никакого объекта не создается явно, с помощью операции new. Язык Java выполнит это автоматически.
Для автораспаковки объекта просто присвойте ссылку на него переменной соответствующего базового типа. Например, для распаковки объекта iOb можно использовать следующую строку кода:
int i = iOb; // автораспаковка
Все детали выполнит для вас язык Java.
В листинге 2.1 приведена короткая программа, вобравшая в себя все приведенные ранее фрагменты и демонстрирующая основы механизма автоупаковки/распаковки,
Листинг 2.1. Демонстрация применения автоупаковки/распаковки
// Demonstrate autoboxing/unboxing.
class AutoBox {
public static void main(String args[]) {
Integer iOb = 100; ; // автоупаковка значения типа int
int i = iOb; // автораспаковка
System.out.println(i + " " + iOb); // отображает на экране: 100 100
}
}
Обратите внимание еще раз на то, что не нужно явно создавать объект типа Integer для упаковки значения 100 и нет необходимости вызывать метод intValue() для распаковки этого значения.
Автоупаковка и методы
Помимо простых случаев присваивания, автоупаковка выполняется автоматически каждый раз, когда данные базового типа должны быть преобразованы в объект, а автораспаковка — при необходимости преобразования объекта в значение базового типа. Следовательно, автоупаковка/распаковка может происходить, когда аргумент передается в метод или когда значение возвращается методом. Рассмотрим пример, приведенный в листинге 2.2.
Листинг 2.2. Автоупаковка/распаковка параметров метода и возвращаемых им значений
// Autoboxing/unboxing takes place with
// method parameters and return values.
class AutoBox2 {
// Принимает параметр типа Integer и возвращает
// значение типа int;
static int m(Integer v) {
return v ; // auto-unbox to int
}
public static void main(String args[]) {
// Передает значение int в метод m() и присваивает возвращаемое
// значение объекту типа Integer. Здесь аргумент 100
// автоупаковывается в объект типа Integer. Возвращаемое значение
// также автоупаковывается в тип Integer.
Integer iOb = m(100);
System.out.println(iOb);
}
}
Программа листинга 2.2 отображает следующий ожидаемый результат:
100
В приведенной программе метод задает параметр типа Integer и возвращает результат типа int. В теле main() методу m() передается значение 100. Поскольку ожидает объект типа Integer, передаваемое значение автоматически упаковывается. Далее метод то возвращает эквивалент своего аргумента, но простого типа int. Это приводит к автоматической распаковке в переменную v. Далее в методе main() объекту iOb присваивается это значение типа int, что вызывает его автоупаковку. Главное преимущество заключается в том, что все преобразования выполняются автоматически.
Автоупаковка/распаковка в выражениях
Вообще, автоупаковка/распаковка происходит всегда, когда требуется преобразование в объект или из объекта. Это применимо и к выражениям. В них числовой объект автоматически распаковывается. Результат выражения повторно упаковывается, если это необходимо. Рассмотрим программу, приведенную в листинге 2.3.
Листинг 2.3. Автоупаковка/распаковка внутри выражений
class AutoBox3 {
public static void main(String args[]) {
Integer iOb, iOb2;
int i;
iOb = 100;
System.out.println("Original value of iOb: " + iOb);
// Далее автоматически распаковывается объект iOb,
// выполняется операция инкремента, затем результат
// повторно упаковывается в объект iOb.
++iOb;
System.out.println("After ++iOb: " + iOb);
// Здесь iOb распаковывается, выражение
// вычисляется и результат повторно упаковывается
// и присваивается iOb2.
iOb2 = iOb + (iOb / 3);
System.out.println("iOb2 after expression: " + iOb2);
// To же самое выражение вычисляется, но результат
// повторно не упаковывается.
i = iOb + (iOb / 3);
System.out.println("i after expression: " + i);
}
}
Далее приведен вывод программы, отображающий результаты ее работы.
Original value of iOb: 100
After ++iOb: 101
iOb2 after expression: 134
i after expression: 134
Обратите особое внимание на следующую строку программы из листинга 2.3:
++iOb;
Она вызывает увеличение на единицу значения, упакованного в объекте iOb. Это действие выполняется следующим образом: объект iOb распаковывается, значение увеличивается, и результат повторно упаковывается.
Автораспаковка позволяет смешивать в выражении числовые объекты разных типов. После того как значения распакованы, к ним применимы стандартные преобразования типов и переходы от одного к другому. Например, приведенная в листинге 2.4 программа вполне корректна.
Листинг 2.4. Обработка числовых объектов разных типов в одном выражении
class AutoBox4 {
public static void main(String args[]) {
Integer iOb = 100;
Double dOb = 98.6;
dOb = dOb + iOb;
System.out.println("dOb after expression: " + dOb);
}
}
Далее приведен результат работы программы из листинга 2.4.
iOb after expression: 198.6
Как видите, и объект dOb типа Double, и объект iOb типа Integer участвовали в сложении, а результат был повторно упакован и сохранен в объекте dOb.
Благодаря автораспаковке Вы можете использовать целочисленный объект для управления оператором switch. Рассмотрим следующий фрагмент программы:
Integer iOb = 2 ;
switch (iOb) {
case 1: System.out.println("one");
break;
case 2: System.out.println("two") ;
break;
default: System.out.println("error") ;
Когда вычисляется выражение в операторе switch, распаковывается объект iOb и извлекается значение типа int.
Приведенные примеры программ показывают, что наличие автоупаковки/распаковки делает использование числовых объектов в выражении легким и интуитивно понятным. В прошлом в этот код пришлось бы вставлять вызовы методов, подобных intValue().
Автоупаковка/распаковка логических и символьных значений
Кроме оболочек для числовых типов язык Java также предоставляет оболочки для данных типов boolean и char. Они называются Boolean и Character соответственно. К ним также применимы автоупаковка/распаковка. Рассмотрим программу, приведенную в листинге 2.5.
Листинг 2.5. Автоупаковка/распаковка типов Boolean и Character
class AutoBox5 {
public static void main(String args[]) {
// Автоупаковка/распаковка логических переменных.
Boolean b = true;
// Далее объект b автоматически распаковывается, когда используется
// в условном выражении оператора, такого как if.
if(b) System.out.println("b is true");
// Автоупаковка/распаковка символьных переменных.
Character ch = 'x'; // box a char
char ch2 = ch; // unbox a char
System.out.println("ch2 is " + ch2);
}
}
Далее приведен вывод программы из листинга 2.5, отображающий результаты ее работы:
b is true
ch2 is x
Наиболее важной в программе из листинга 2.5 является автораспаковка объекта b внутри условного выражения в операторе if. Как вы должны помнить, условное выражение, управляющее выполнением оператора if, следует вычислять как значение типа boolean. Благодаря наличию автораспаковки логическое значение, содержащееся в объекте b, автоматически распаковывается при вычислении условного выражения. Таким образом, с появлением Java 2 v5.0 стало возможным использование объекта типа Boolean для управления оператором if.
Более того, теперь объект типа Boolean можно применять для управления любыми операторами цикла языка Java. Когда объект типа Boolean используется как условное выражение в циклах while, for, do/while, он автоматически распаковывается в эквивалент простого типа boolean. Например, приведенный далее фрагмент теперь абсолютно корректен.
Boolean b;
//
while (b) { //
Помощь автоупаковки/распаковки в предупреждении ошибок
Кроме удобства, которое предоставляет механизм автоупаковки/распаковки, он может помочь в предупреждении ошибок. Рассмотрим программу, приведенную в листинге 2.6.
Листинг 2.6. Ошибка, возникшая при распаковке вручную
class UnboxingError {
public static void main(String args[]) {
Integer iOb = 1000;
// автоматически упаковывает значение 1000
int i = iOb.byteValue();// вручную распаковывается как тип byte !!!
System.out.println(i);// не отображает значение 1000
}
}
Программа из листинга 2.6 отображает число -24 вместо ожидаемого значения 1000! Причина заключается в том, что значение, хранящееся в объекте iOb, распаковывается вручную с помощью вызова метода byteValue() который приводит к усечению этого значения, равного 1000. В результате переменной i присваивается число -24, так называемый "мусор". Автораспаковка препятствует возникновению ошибок этого типа, потому что она преобразует значение, хранящееся в iOb, в величину базового типа, сопоставимого с типом int.
Вообще говоря, поскольку автоупаковка всегда создает правильный объект, а автораспаковка всегда извлекает надлежащее значение, не возникает ситуаций для формирования неверного типа объекта или значения. В редких случаях, когда Вам нужен тип, отличающийся от созданного автоматическим процессом, Вы и сейчас можете упаковывать и распаковывать значения вручную так, как делали это раньше. Конечно, при этом теряются преимущества автоупаковки/распаковки. Как правило, во вновь разрабатываемом коде должны применяться эти механизмы, так как они соответствуют современному стилю программирования на языке Java.
Предостережения
Теперь, когда в язык Java включены средства автоупаковки/распаковки, может появиться желание использовать только числовые объекты типа Integer или Double, полностью отказавшись от данных простых типов. Например, благодаря наличию автоупаковки/распаковки теперь можно написать код, подобный приведенному далее.
//Пример плохого использования автоупаковки/распаковки Double a,b,c;
а = 10.0;
b = 4.0;
с = Math.sqrt(a*a + b*b);
System.out.println("Hypotenuse is " + c);
В приведенном примере объекты типа Double содержат значения, которые используются для вычисления гипотенузы прямоугольного треугольника. Хотя этот код технически корректен и будет выполняться правильно, он служит образцом очень плохого применения автоупаковки/распаковки. Гораздо эффективнее использовать данные простого типа double для подобных вычислений, т. к. каждая автоупаковка и автораспаковка вносят дополнительные затраты, которых лишены вычисления с применением базовых типов данных.
Вообще говоря, следует ограничить использование оболочек типов только теми случаями, для которых требуется объектное представление данных простых типов. Автоупаковка/автораспаковка включены в язык таким образом, чтобы не ограничивать применение простых типов данных.
|