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