Автоупаковка и распаковка в Java.

В этой статье мы рассмотрим такую функцию в Java как автоупаковка/распаковка.
Автоупаковка и распаковка это функция преобразования примитивных типов в объектные и наоборот.

Весь процесс выполняется автоматически средой выполнения Java (JRE). Но следует быть осторожным при реализации этой функции, т.к. Она может влиять на производительность вашей программы.

Введение:
В версиях ниже JDK 1.5 было не легко преобразовывать примитивные типы данных, такие как
int, char, float, double в их классы оболочки Integer, Character, Float, Double. Начиная с версии JDK 5 эта функция, преобразования примитивных типов в эквивалентные объекты, реализована автоматически.
Это свойство известно как Автоупаковка(Autoboxing). Обратный процесс соответственно – Распаковка(Unboxing) т.е. процесс преобразования объектов в соответствующие им примитивные типы.

Пример кода для автоупаковки и распаковки представлен ниже:

Автоупаковка

1 Integer integer = 9;


Распаковка

1 int in = 0;
2 in = new Integer(9);


Когда используется автоупаковка и распаковка?

Автоупаковка применяется компилятором Java в следующих условиях:

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

Рассмотрим следующий пример:

Листинг 1: Простой код, показывающий автоупаковку

1 public int sumEvenNumbers(List<Integer> intList ) {
2    int sum = 0;
3    for (Integer i: intList )
4      if ( i % 2 == 0 )
5        sum += i;
6      return sum;
7 }


До версии jdk 1.5 приведенный выше код вызвал бы ошибку компиляции, так как оператор получения остатка % и унарный плюс += не могли применяться к классу-оболочке.
Но в jdk 1.5 и выше этот код компилируется без ошибок, преобразовывая Integer в int.

Распаковка применяется компилятором Java в следующих условиях:

Когда объект передается в качестве параметра методу, который ожидает соответствующий примитивный тип.
Когда объект присваивается переменной соответствующего примитивного типа.

Рассмотрим следующий пример:

Листинг 2: Простой код, показывающий распаковку

1   import java.util.ArrayList;
2   import java.util.List;
3
4   public class UnboxingCheck {
5
6      public static void main(String[] args) {
7        Integer in = new Integer(-8);
8
9        // 1. Распаковка через вызов метода
10        int absVal = absoluteValue(in);
11        System.out.println("absolute value of " + in + " = " + absVal);
12
13        List<Double> doubleList = new ArrayList<Double>();
14
15        // Автоупаковка через вызов метода
16        doubleList.add(3.1416);
17
18        // 2. Распаковка через присвоение
19        double phi = doubleList.get(0);
20        System.out.println("phi = " + phi);
21    }
22
23    public static int absoluteValue(int i) {
24        return (i < 0) ? -i : i;
25    }
26  }

Автоупаковка и распаковка позволяют разработчику писать код, который легко читается и понятен. Следующая таблица показывает примитивные типы данных и их соответствующие объекты оболочки.
Примитивные типы Классы оболочки
boolean Boolean
byte Byte
char Character
float Float
int Integer
long Long
short Short

Таблица 1: Примитивные типы и эквивалентные им классы оболочки

С операторами сравнения
Автоупаковка и распаковка могут использоваться с операторами сравнения. Следующий фрагмент кода иллюстрирует, как это происходит:

Листинг 3: Пример кода, показывающий автоупаковку и распаковку с оператором сравнения

1  public class BoxedComparator {
2    public static void main(String[] args) {
3        Integer in = new Integer(25);
4        if (in < 35)
5            System.out.println("Value of int = " + in);
6    }
7  }


Автоупаковка и распаковка при перегрузке метода

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

Листинг 4: Пример кода, показывающий преимущество перегрузки

1  public class WideBoxed {
2    public class WideBoxed {
3    static void methodWide(int i) {
4         System.out.println("int");
5     }
6 
7    static void methodWide( Integer i ) {
8        System.out.println("Integer");
9    }
10
11    public static void main(String[] args) {
13        short shVal = 25;
14        methodWide(shVal);
15    }
16   }
17  }


Вывод программы — тип int

— Расширение побеждает переменное количество аргументов – В ситуации, когда становится выбор между расширением и переменным количеством аргументов, расширение предпочтительней.

Листинг 5: Пример кода, показывающий преимущество перегрузки

1  public class WideVarArgs {
2
3    static void methodWideVar(int i1, int i2) {
4      System.out.println("int int");
5    }
6
7    static void methodWideVar(Integer... i) {
8       System.out.println("Integers");
9    }
10
11   public static void main( String[] args) {
12       short shVal1 = 25;
13      short shVal2 = 35;
14     methodWideVar( shVal1, shVal2);
15   }
16  }


— Упаковка побеждает переменное количество аргументов – В ситуации, когда становится выбор между упаковкой и переменным количеством аргументов, упаковка предпочтительней.

Листинг 6: Пример кода, показывающий преимущество перегрузки

1  public class BoxVarargs {
2      static void methodBoxVar(Integer in) {
3          System.out.println("Integer");
4      }
5     
6      static void methodBoxVar(Integer... i) {
7          System.out.println("Integers");
8      }     
9      public static void main(String[] args) {
10          int intVal1 = 25;
11          methodBoxVar(intVal1);
12     }
13 }


Вы должны помнить о следующих вещах, используя Автоупаковку:
Как мы знаем, любая хорошая функция имеет недостаток. Автоупаковка не является исключением в этом отношении. Некоторый важные замечания, которые должен учитывать разработчик при использовании этой функции:
— Сравнивая объекты оператором ‘==’ может возникнуть путаница, так как он может применяться к примитивным типам и объектам. Когда этот оператор применяется к объектам, он фактически сравнивает ссылки на объекты а не сами объекты.

Листинг 7: Пример кода, показывающий сравнение.

1   public class Comparator {
2      public static void main(String[] args) {
3        Integer istInt = new Integer(1);
4          Integer secondInt = new Integer(1);
5
6          if (istInt == secondInt) {
7            System.out.println("both one are equal");
8 
9          } else {
10             System.out.println("Both one are not equal");
11          }
12      }
13   }


— Смешивание объектов и примитивных типов с оператором равенства и отношения. Если мы сравниваем примитивный тип с объектом, то происходит распаковывание объекта, который может бросить NullPointerException если объект null.

— Кэширование объектов. Метод valueOf() создает контейнер примитивных объектов, которые он кэширует. Поскольку значения кэшируются в диапазоне от -128 до 127 включительно, эти кэшируемые объекты могут себя вести по-разному.

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

Недостатки Автоупаковки.
Хотя Автоупаковка имеет ряд преимуществ, она имеет следующие недостатки:

Листинг 8: Пример кода, показывающий проблему производительности.

1  public int sumEvenNumbers(List<Integer> intList) {
2          int sum = 0;
3          for (Integer i : intList) {
4              if (i % 2 == 0) {
5                  sum += i;
6              }
7          }
8          return sum;
9   }


В этом участке кода, 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. Распаковка происходит в обратном направлении.
Оригинал статьи

4 комментария

4joke
Кажется тут фигурные скобки у цикла пропущены.
Листинг 1: Простой код, показывающий автоупаковку

1 public int sumEvenNumbers(List<Integer> intList ) {
2    int sum = 0;
3    for (Integer i: intList )
4      if ( i % 2 == 0 )
5        sum += i;
6      return sum;
7 }
CynepHy6
а если так?)
public int sumEvenNumbers(List<Integer> intList ) {
    int sum = 0;
    for (Integer i: intList )
        if ( i % 2 == 0 )
            sum += i;
    return sum;
}
artie
Касательно пятого листинга, там не совсем правда. Если сделать так:

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);
   }
  }

выдаст ошибку (java.lang.Integer...) cannot be applied to (short, short).
Litle
Смею предположить (если есть что поправить, то будет гуд)
Когда компилятор встречает вызов метода со списком параметров переменной длины, то он упаковывает эти элементы в массив. Т.е. на входе должен быть массив Integer[]. Мы подаем short[]. При этом для массивов
autoboxing не работает: stackoverflow.com/questions/1770894/java-why-isnt-autoboxing-happening-here.

В примере кода изменяем сигнатуру вызываемого метода на int... и всё скомпилируется.
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.