JavaRush /Java блог /Java Developer /Обёртки, распаковка и запаковка
Автор
Aditi Nawghare
Инженер-программист в Siemens

Обёртки, распаковка и запаковка

Статья из группы Java Developer
Привет! Ты уже неплохо знаком с примитивными типами, и немало с ними поработал. Обёртки, распаковка и запаковка - 1У примитивов в программировании, и в Java в частности, есть множество преимуществ: они занимают мало памяти, за счет чего повышается эффективность работы программы, и четко разделены по диапазонам значений. Однако в процессе изучения Java мы уже не раз, словно мантру, повторяли — “в Java все является объектом”. А ведь примитивы — прямое опровержение этих слов. Объектами они не являются. Получается, принцип “все является объектом” является ложным? На самом деле нет. В Java у каждого примитивного типа есть свой брат-близнец — класс-обертка (Wrapper). Что такое обертка? Обертка — это специальный класс, который хранит внутри себя значение примитива. Но поскольку это именно класс, он может создавать свои экземпляры. Они будут хранить внутри нужные значения примитивов, при этом будут являться настоящими объектами. Названия классов-оберток очень похожи на названия соответствующих примитивов, или полностью с ними совпадают. Поэтому запомнить их будет очень легко.
Wrapper Classes for Primitive Data Types
Primitive Data Types Wrapper Classes
int Integer
short Short
long Long
byte Byte
float Float
double Double
char Character
boolean Boolean
Объекты классов оберток создаются так же, как и любые другие:

public static void main(String[] args) {

   Integer i = new Integer(682);
  
   Double d = new Double(2.33);
  
   Boolean b = new Boolean(false);
}
Классы-обертки позволяют нивелировать недостатки, которые есть у примитивных типов. Самый очевидный из них — примитивы не имеют методов. Например, у них нет метода toString(), поэтому ты не сможешь, например, преобразовать число int в строку. А вот с классом-оберткой Integer — запросто.

public static void main(String[] args) {

   Integer i = new Integer(432);
  
   String s = i.toString();
}
Возникнут сложности и с обратным преобразованием. Допустим, у нас есть строка, про которую мы точно знаем, что она содержит число. Тем не менее, в случае с примитивным типом int мы никак не сможем это число из строки достать и превратить, собственно, в число. Но благодаря классам-оберткам такая возможность у нас появилась.

public static void main(String[] args) {

   String s = "1166628";

   Integer i = Integer.parseInt(s);

   System.out.println(i);
}
Вывод: 1166628 Мы успешно получили число из строки и присвоили его в переменную-ссылку Integer i. Кстати, по поводу ссылок. Ты уже знаешь, что параметры передаются в методы по-разному: примитивы — по значению, а объекты — по ссылке. Ты можешь использовать это знание при создании своих методов: если твой метод работает, например, с дробными числами, но тебе нужна логика именно передачи по ссылке, ты можешь передать в метод параметры Double/Float вместо double/float. Кроме того, помимо методов в классах-обертках есть очень удобные для использования статические поля. Например, представь, что перед тобой сейчас стоит задача: вывести в консоль максимально возможное число int, а после — минимально возможное. Задачка вроде элементарная, а все равно — без гугла вряд ли справишься. А классы-обертки легко позволяют решать такие “бытовые задачи”:

public class Main {
   public static void main(String[] args) {

       System.out.println(Integer.MAX_VALUE);
       System.out.println(Integer.MIN_VALUE);
   }
}
Такие поля позволяют не отвлекаться от выполнения более серьезных задач. Не говоря уж о том, что в процессе печати числа 2147483647 (это как раз MAX_VALUE) не мудрено и опечататься:) Кроме того, в одной из прошлых лекций мы уже обращали внимание на то, что объекты классов-оберток являются неизменяемыми (Immutable).

public static void main(String[] args) {

   Integer a = new Integer(0);
   Integer b = new Integer(0);

   b = a;
   a = 1;
   System.out.println(b);
}
Вывод: 0 Объект, на который изначально указывала ссылка а, не изменил свое состояние, иначе значение b тоже изменилось бы. Как и в случае со String, вместо изменения состояния объекта-обертки в памяти создается абсолютно новый объект. Почему же создатели Java, в конечном итоге, приняли решение оставить в языке примитивные типы? Раз уж все должно являться объектом, и у нас уже есть классы-обертки, которыми можно выразить все, что выражают примитивы, почему вообще не оставить в языке только их, а примитивы удалить? Ответ прост — производительность. Примитивные типы потому и называют примитивными, потому что они лишены многих “тяжеловесных” особенностей объектов. Да, у объекта есть много удобных методов, но ведь они не всегда тебе нужны. Иногда тебе нужно просто число 33, или 2,62, или значение true/false. В ситуациях, когда все преимущества объектов не имеют значения и не нужны для работы программы, примитивы справятся с задачей гораздо лучше.

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

Одной из особенностей примитивов и их классов-оберток в Java является автоупаковка/автораспаковка (Autoboxing/Autounboxing) Обёртки, распаковка и запаковка - 2 Давай разберемся с этим понятием. Как мы с тобой уже узнали ранее, Java — объектно-ориентированный язык. Это значит, что все программы, написанные на Java, состоят из объектов. Примитивы не являются объектами. Но при этом переменной класса-обертки можно присваивать значение примитивного типа. Этот процесс называется автоупаковкой (autoboxing). Точно так же переменной примитивного типа можно присваивать объект класса-обертки. Этот процесс называется автораспаковкой (autounboxing). Например:

public class Main {
   public static void main(String[] args) {
       int x = 7;
       Integer y = 111;
       x = y; // автораспаковка
       y = x * 123; // автоупаковка
   }
}
В строке 5 мы присваиваем примитиву x значение y, который является объектом класса-обертки Integer. Как видишь, никаких дополнительных действий для этого не нужно: компилятор знает что int и Integer, по сути, одно и то же. Это и есть автораспаковка. Так же происходит и автоупаковка в строке 6: объекту y легко присваивается значение примитивов (х*123). Это пример автоупаковки. Именно поэтому добавляется слово "авто": для присваивания ссылок-примитивов объектам их классов-оберток (и наоборот) не требуется ничего делать, все происходит автоматически. Удобно, да? :) Еще одно очень большое удобство автоупаковки/автораспаковки проявляется в работе методов. Дело в том, что параметры методов тоже подлежат автоупаковке и автораспаковке. И, например, если какой-то из них принимает на вход два объекта Integer — мы легко можем передать туда обычные примитивы int!

public class Main {
   public static void main(String[] args) {

       printNumber(7);//обычный int, даже без переменной
   }

   public static void printNumber(Integer i) {
       System.out.println("Вы ввели число " + i);
   }
}
Вывод: Вы ввели число 7 Точно так же работает и наоборот:

public class Main {
   public static void main(String[] args) {

       printNumber(new Integer(632));
   }

   public static void printNumber(int i) {
       System.out.println("Вы ввели число " + i);
   }
}
Важный момент, о котором нужно помнить: автоупаковка и распаковка не работают для массивов!

public class Main {
   public static void main(String[] args) {

       int[] i = {1,2,3,4,5};
      
       printArray(i);//ошибка, не компилируется!
   }
  
   public static void printArray(Integer[] arr) {
       System.out.println(Arrays.toString(arr));
   }
}
Попытка передать массив примитивов в метод, который принимает на вход массив объектов, вызовет ошибку компиляции. Напоследок, еще раз кратко сравним примитивы и обертки Примитивы:
  • имеют преимущество в производительности
Обертки:
  • Позволяют не нарушать принцип “все является объектом”, благодаря чему числа, символы и булевы значения true/false не выпадают из этой концепции
  • Расширяют возможности работы с этими значениями, предоставляя удобные методы и поля
  • Необходимы, когда какой-то метод может работать исключительно с объектами
Комментарии (183)
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
Kaz Уровень 17
5 февраля 2024
Мне нужна помощь тут:

public static void main(String[] args) {

   Integer a = new Integer(0); //создали объект Integer, назвали a, передали ссылку на 0
   Integer b = new Integer(0);   //создали объект Integer, назвали b, передали ссылку на 0

   b = a;   //объекту b ссылающемуся на 0, попытались присвоить ссылку от а, 
               //который тоже ссылался на 0. Ок, допустим, объект b immortable и
              //и изменяться он не собирается, но ведь он все равно ссылается на 0

   a = 1;  //после этого объекту а, передают ссылку на 1.. ну допустим, теперь
             //создается новый объект a, потому что первый менять нельзя (или  я просто 
             //понимаю суть его неменяемости).. но дело не в этом. 

   System.out.println(b);
           //дело в том, что чего удивляться что в консоль выводится 0. Конечно, там будет 0!
}
и конечно, если сделать наоборот:

   Integer a = new Integer(0);
   Integer b = new Integer(0);
    a = 1;
   b = a;
 
   System.out.println(b);
То тоже ничего удивительно - выведется 1! Короче, о чем был этот абзац в лекции? О_о
Магсумова Диана Уровень 108 Expert
24 января 2024
Очень хорошая статья 👍
Roman Kibenko Уровень 9
18 января 2024
Не все в Java является объектом, кроме того, что все)
Максим Li Уровень 36
29 ноября 2023
Всё хорошо!
IT Уровень 19
14 ноября 2023
Если вы так и не поняли, то примитивы объектами не являются)
Anonymous #3361169 Уровень 47
1 ноября 2023
👍
Артур Уровень 33
29 сентября 2023
Отличная статья, как и все этого Автора
Anatoly Уровень 30
17 сентября 2023
понятно
26 июля 2023
не всё в java является объектом, например в java существуют операторы
Mamba84 Уровень 30
17 июля 2023
Thank you, Aditi Nawghare!