В этой статье мы рассмотрим такую функцию в Java как
автоупаковка/распаковка. Автоупаковка и распаковка это функция преобразования примитивных типов в объектные и наоборот.
![Автоупаковка и распаковка в Java - 1]()
Весь процесс выполняется автоматически средой выполнения Java (JRE). Но следует быть осторожным при реализации этой функции, т.к. Она может влиять на производительность вашей программы.
Введение
В версиях ниже JDK 1.5 было не легко преобразовывать примитивные типы данных, такие как
int
,
char
,
float
,
double
в их классы оболочки Integer, Character, Float, Double. Начиная с версии JDK 5 эта функция, преобразования примитивных типов в эквивалентные объекты, реализована автоматически.
Это свойство известно как
Автоупаковка (Autoboxing). Обратный процесс соответственно –
Распаковка (Unboxing) т.е. процесс преобразования объектов в соответствующие им примитивные типы.
Пример кода для автоупаковки и распаковки представлен ниже:
Автоупаковка
Integer integer = 9;
Распаковка
int in = 0;
in = new Integer(9);
Когда используется автоупаковка и распаковка?
Автоупаковка применяется компилятором Java в следующих условиях:
- Когда значение примитивного типа передается в метод в качестве параметра метода, который ожидает объект соответствующего класса-оболочки.
- Когда значение примитивного типа присваивается переменной, соответствующего класса оболочки.
Рассмотрим следующий пример:
Листинг 1: Простой код, показывающий автоупаковку
public int sumEvenNumbers(List<Integer> intList ) {
int sum = 0;
for (Integer i: intList )
if ( i % 2 == 0 )
sum += i;
return sum;
}
До версии jdk 1.5 приведенный выше код вызвал бы ошибку компиляции, так как оператор получения остатка % и унарный плюс += не могли применяться к классу-оболочке. Но в jdk 1.5 и выше этот код компилируется без ошибок, преобразовывая Integer в
int
.
Распаковка применяется компилятором Java в следующих условиях:
- Когда объект передается в качестве параметра методу, который ожидает соответствующий примитивный тип.
- Когда объект присваивается переменной соответствующего примитивного типа.
Рассмотрим следующий пример:
Листинг 2: Простой код, показывающий распаковку
import java.util.ArrayList;
import java.util.List;
public class UnboxingCheck {
public static void main(String[] args) {
Integer in = new Integer(-8);
// 1. Распаковка через вызов метода
int absVal = absoluteValue(in);
System.out.println("absolute value of " + in + " = " + absVal);
List<Double> doubleList = new ArrayList<Double>();
// Автоупаковка через вызов метода
doubleList.add(3.1416);
// 2. Распаковка через присвоение
double phi = doubleList.get(0);
System.out.println("phi = " + phi);
}
public static int absoluteValue(int i) {
return (i < 0) ? -i : i;
}
}
Автоупаковка и распаковка позволяют разработчику писать код, который легко читается и понятен. Следующая таблица показывает примитивные типы данных и их соответствующие объекты оболочки.
Примитивные типы | Классы оболочки |
boolean | Boolean |
byte | Byte |
char | Character |
float | Float |
int | Integer |
long | Long |
short | Short |
Таблица 1: Примитивные типы и эквивалентные им классы оболочки c операторами сравнения
Автоупаковка и распаковка могут использоваться с операторами сравнения. Следующий фрагмент кода иллюстрирует, как это происходит:
Листинг 3: Пример кода, показывающий автоупаковку и распаковку с оператором сравнения
public class BoxedComparator {
public static void main(String[] args) {
Integer in = new Integer(25);
if (in < 35)
System.out.println("Value of int = " + in);
}
}
Автоупаковка и распаковка при перегрузке метода
Автоупаковка и распаковка выполняется при перегрузке метода на основании следующих правил:
- Расширение "побеждает" упаковку в ситуации, когда становится выбор между расширением и упаковкой, расширение предпочтительней.
Листинг 4: Пример кода, показывающий преимущество перегрузки
public class WideBoxed {
public class WideBoxed {
static void methodWide(int i) {
System.out.println("int");
}
static void methodWide( Integer i ) {
System.out.println("Integer");
}
public static void main(String[] args) {
short shVal = 25;
methodWide(shVal);
}
}
}
Вывод программы — тип
int
- Расширение побеждает переменное количество аргументов в ситуации, когда становится выбор между расширением и переменным количеством аргументов, расширение предпочтительней.
Листинг 5: Пример кода, показывающий преимущество перегрузки
public class WideVarArgs {
static void methodWideVar(int i1, int i2) {
System.out.println("int int");
}
static void methodWideVar(Integer... i) {
System.out.println("Integers");
}
public static void main( String[] args) {
short shVal1 = 25;
short shVal2 = 35;
methodWideVar( shVal1, shVal2);
}
}
- Упаковка побеждает переменное количество аргументов в ситуации, когда становится выбор между упаковкой и переменным количеством аргументов, упаковка предпочтительней.
Листинг 6: Пример кода, показывающий преимущество перегрузки
public class BoxVarargs {
static void methodBoxVar(Integer in) {
System.out.println("Integer");
}
static void methodBoxVar(Integer... i) {
System.out.println("Integers");
}
public static void main(String[] args) {
int intVal1 = 25;
methodBoxVar(intVal1);
}
}
Вы должны помнить о следующих вещах, используя Автоупаковку:
Как мы знаем, любая хорошая функция имеет недостаток. Автоупаковка не является исключением в этом отношении. Некоторый важные замечания, которые должен учитывать разработчик при использовании этой функции:
- Сравнивая объекты оператором ‘
==
’ может возникнуть путаница, так как он может применяться к примитивным типам и объектам. Когда этот оператор применяется к объектам,он фактически сравнивает ссылки на объекты а не сами объекты.
Листинг 7: Пример кода, показывающий сравнение.
public class Comparator {
public static void main(String[] args) {
Integer istInt = new Integer(1);
Integer secondInt = new Integer(1);
if (istInt == secondInt) {
System.out.println("both one are equal");
} else {
System.out.println("Both one are not equal");
}
}
}
- Смешивание объектов и примитивных типов с оператором равенства и отношения. Если мы сравниваем примитивный тип с объектом, то происходит распаковывание объекта, который может бросить
NullPointerException
если объект null
.
- Кэширование объектов. Метод
valueOf()
создает контейнер примитивных объектов, которые он кэширует. Поскольку значения кэшируются в диапазоне от -128 до 127 включительно , эти кэшируемые объекты могут себя вести по-разному.
- Ухудшение производительности. Автоупаковка или распаковка ухудшают производительность приложения, поскольку это создает нежелательный объект, из-за которого сборщику мусора приходится работать чаще.
Недостатки Автоупаковки
Хотя Автоупаковка имеет ряд преимуществ, она имеет следующие недостатки:
Листинг 8: Пример кода, показывающий проблему производительности.
public int sumEvenNumbers(List intList) {
int sum = 0;
for (Integer i : intList) {
if (i % 2 == 0) {
sum += i;
}
}
return sum;
}
В этом участке кода,
sum +=i
будет расширен на
sum = sum + i
. Начиная с оператора ‘
+
’ JVM запускает распаковку, так как оператор ‘
+
’ не может применяться к объекту Integer. А затем результат автоупаковывается обратно.
До версии JDK 1.5 типы данных
int
и Integer различались. В случае перегрузки метода эти два типа использовались без проблем. С появление автоматической упаковки/распаковки это стало сложней.
Примером этого является перегруженный метод
remove()
в
ArrayList
. Класс
ArrayList
имеет два метода удаления —
remove(index)
и
remove(object)
. В этом случае перегрузка методов не произойдет и соответствующий метод будет вызываться с соответствующими параметрами.
Заключение
Автоупаковка является механизмом для скрытого преобразования примитивных типов данных в соответствующие классы-оболочки(объекты). Компилятор использует метод
valueOf()
чтобы преобразовать примитивные типы в объекты, а методы
IntValue()
,
doubleValue()
и т.д., чтобы получить примитивные типы объекта. Автоупаковка преобразует логический тип
boolean
в Boolean,
byte
в Byte,
char
в Character,
float
в Float,
int
в Integer,
long
в Long,
short
в Short. Распаковка происходит в обратном направлении.
Оригинал статьи
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
выдаст ошибку (java.lang.Integer...) cannot be applied to (short, short).
Когда компилятор встречает вызов метода со списком параметров переменной длины, то он упаковывает эти элементы в массив. Т.е. на входе должен быть массив Integer[]. Мы подаем short[]. При этом для массивов
autoboxing не работает: stackoverflow.com/questions/1770894/java-why-isnt-autoboxing-happening-here.
В примере кода изменяем сигнатуру вызываемого метода на int... и всё скомпилируется.