Сегодня речь пойдет об автоупаковке (autoboxing) и распаковке (unboxing). Это одно из существенных изменений, внесенных в JDK 5. Теперь разработчики могут писать более чистый код, но непонимание работы этого механизма может привести к плохой производительности.

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


Это автоматическая инкапсуляция примитивного типа в эквивалентную ему класс-обёртку всякий раз, когда требуется объект данного типа. Про инкапсуляцию и другие принципы ООП существует замечательная статья от ffriend.

Autoboxing происходит:
  1. При присвоении значения примитивного типа переменной соответствующего класса-обёртки.
  2. При передаче примитивного типа в параметр метода, ожидающего соответствующий ему класс-обёртку.

Примеры


До JDK 5
public class Main {
    public static void main(String[] args) {
       Integer iOb = new Integer(7);
       Double dOb = new Double(7.0);
       Character cOb = new Character('a');
       Boolean bOb = new Boolean(true);

       method(new Integer(7));
    }

    public static void method(Integer iOb) {
      System.out.println("Integer");
    }
}

Начиная с JDK 5
public class Main {
    public static void main(String[] args) {
       Integer iOb = 7;
       Double dOb = 7.0;
       Character cOb = 'a';
       Boolean bOb = true;

       method(7);
    }

    public static void method(Integer iOb) {
      System.out.println("Integer");
    }
}


Автораспаковка


Это преобразование класса-обёртки в соответствующий ему примитивный тип. Если при распаковке класс-обёртка был равен null, произойдет исключение java.lang.NullPointerException.

Unboxing происходит:
  1. При присвоении экземпляра класса-обёртки переменной соответствующего примитивного типа.
  2. В выражениях, в которых один или оба аргумента являются экземплярами классов-обёрток (кроме операции == и !=).
  3. При передаче объекта класса-обёртки в метод, ожидающий соответствующий примитивный тип.

Давайте рассмотрит более детально.

1. При присвоении


До JDK 5
int i = iOb.intValue();
double d = dOb.doubleValue();
char c = cOb.charValue();
boolean b = bOb.booleanValue();

Начиная с JDK 5
int i = iOb;
double d = dOb;
char c = cOb;
boolean b = bOb;

2. В выражениях


Так как арифметические операторы и операторы сравнения (исключение == и !=) применяются только к примитивным типам, приходилось делать распаковку вручную, что заметно снижало читабельность выражений, делая их громоздкими, и кода в целом.
Integer iOb1 = new Integer(5);
Integer iOb2 = new Integer(7);
System.out.println(iOb1.intValue() > iOb2.intValue());

Благодаря автораспаковке, мы смело можем писать выражения, не используя методы конвертации. Теперь за этим следит компилятор Java.
System.out.println(iOb1 > iOb2);
System.out.println(iOb1 + iOb2);

При сравнении классов-обёрток оператором == или !=, происходит сравнение по ссылкам, а не по значениям и может возникнуть путаница. Например, как вы думаете, что выведется на экран при исполнении следующего кода?
Integer iOb1 = 100;
Integer iOb2 = 100;
System.out.println(iOb1 == iOb2);

Integer iOb3 = new Integer(120);
Integer iOb4 = new Integer(120);
System.out.println(iOb3 == iOb4);

Integer iOb5 = 200;
Integer iOb6 = 200;
System.out.println(iOb5 == iOb6);

Ответ: в первом случае — true, во втором и третьем — false.
В первом случае фактически вызывается статичный метод java.lang.Integer.valueOf(int), который кэширует значения от -128 до 127 (верхнюю границу можно изменять) и при повторном использовании достает их из так называемого pool (набор инициализированных и готовых к использованию объектов). Во втором происходит явное создание объектов, следовательно они имеют разные ссылки.

3. При передачи в метод


public class Main {
    public static void main(String[] args) {
        Integer iOb  = 10;
        method(iOb);
    }

    public static void method(int i) {
       System.out.println("int");
   }
}

На экран будет выведено int, но стоит отметить, что если для метода реализована перегрузка с соответствующим классом-обёрткой, вызовется именно он.
public class Main {
    public static void main(String[] args) {
        Integer iOb  = 10;
        method(iOb);
    }

    public static void method(int i) {
       System.out.println("int");
   }

    public static void method(Integer iOb) { //Будет вызван данный метод
       System.out.println("Integer");
   }
}

Так же следует помнить, что автоупаковка и автораспаковка не работает для массивов.
public class Main {
    public static void main(String[] args) {
        Integer[] iObs = new Integer[] {5, 10, 50, 2, 7};
        method(iObs); //Ошибка компиляции
    }

    public static void method(int ... i) {
       System.out.println("int[]");
   }
}

Плохая производительность


Классы-обёртки неизменяемые, поэтому при каждой автоупаковке (за исключением значений из pool) создается новый объект, что может привести к неразумному расходу памяти.
public static Integer sumBeforeInclusive(Integer number) {
        Integer iOb = number;
        if (number > 1) iOb += sumBeforeInclusive(number - 1);
        return iOb;
    }

Примитивные типы и их классы-обертки


Целые числа
  • byte — Byte
  • short — Short
  • int — Integer
  • long — Long


Числа с плавающей точкой
  • float — Float
  • double — Double


Символы
  • char — Character


Логические значения
  • boolean — Boolean

Поделиться с друзьями
-->

Комментарии (10)


  1. Koobeton
    25.05.2017 22:11
    +16

    Это одно из существенных изменений, внесенных в JDK 5
    Об этом правда стоит писать в 2017?


  1. lukdiman
    25.05.2017 23:56

    Лучше бы написали о возможных NPE при анбоксинге. Далеко не все об этом помнят.


    1. c0di323
      26.05.2017 07:40

      NullPointerException кидает при попытке обратиться по null ссылке, следовательно, если класс-обёртка содержит null, при распаковке возникнет исключение.


    1. lany
      27.05.2017 11:39

      Главное чтобы ваша IDE помнила и предупреждала при случае :-)


  1. c0di323
    26.05.2017 09:18

    В первом объекты не создаются, а берутся из pool и классы-обёртки имеют одну ссылку. В третьем же создаётся два объекта, потому что 200 не входит в диапазон Integer pool (-128..127), то есть у них разные ссылки. Если у примитивных типов оператор == сравнивает значение (если необходимо, операнды распространяются до наибольшего, затем происходит непосредственное побитовое сравнение), то у объектов проверяет указывают ли ссылки «на один и тот же объект».


    1. lany
      27.05.2017 11:43

      Таки почему «из так называемого pool»? Никто его так не называет. Это так называемый integer cache — кэш целых чисел. И в документации, и в исходниках это называется кэш.


    1. lightdelay
      30.05.2017 07:53

      вызывается статичный метод java.lang.Integer.valueOf(int), который кэширует значения от -128 до 127

      Никакого кэшировани при вызове не будет происходить. IntegerCache инициализируется во время загрузки класса.


      1. c0di323
        30.05.2017 07:53

        Верно, ошибся


  1. Truppv
    26.05.2017 09:22

    Integer iOb1 = 100;
    Integer iOb2 = 100;
    System.out.println(iOb1 == iOb2);


    Integer iOb3 = new Integer(120);
    Integer iOb4 = new Integer(120);
    System.out.println(iOb3 == iOb4);


    Integer iOb5 = 200;
    Integer iOb6 = 200;
    System.out.println(iOb5 == iOb6);


    Ответ: в первом случае — true, во втором и третьем — false


    А в чем разница между 1 и 3 случаями?


    1. c0di323
      26.05.2017 09:25

      Извини, написал как комментарий, смотри чуть выше.