Упаковка и Распаковка (Boxing) — JS: Введение в ООП
Любой метод — это функция в свойстве объекта. Когда мы вызываем метод, то вызываем функцию из свойства:
const obj = sayHello: () => console.log('hello!'), >; obj.sayHello();
Однако JavaScript позволяет вызывать функции не только на объектах, но и на примитивных значениях:
'hexlet'.toUpperCase(); // "HEXLET"
Из этого кода можно сделать вывод, что строка — это тоже объект. На самом деле, этот вывод ошибочный. В JavaScript строки, логические значения, null и числа реализованы как примитивные значения, то есть у них нет методов.
Во время вызова методов на примитивных значениях JavaScript автоматически упаковывает значение в объект и вызывает метод на этом объекте. После этого объект автоматически распаковывается на примитивное значение.
Для каждого примитивного типа в JavaScript есть конструктор, который создает объект из примитивного значения. Именно он и вызывается, когда происходит упаковка.
Чтобы разобраться в этой теме, выполним ручную упаковку на примере выше. Конструктором для строк является String :
const name = new String('hexlet'); console.log(`$name>`); // "hexlet"
Здесь мы создали объект name и упаковали в него примитивное значение — строку ‘hexlet’ .
Дальше происходит распаковка примитивного значения из объекта. Для этого JavaScript вызывает на объекте метод valueOf() :
const name = new String('hexlet'); // Его можно вызвать самостоятельно name.valueOf(); // "hexlet"
То же самое касается других типов:
const number = new Number(100); number.valueOf(); // 100 const bool = new Boolean(true); bool.valueOf(); // true
Метод valueOf() вызывается в результате разных операций над объектом:
const number = new Number(100); const newName = `$number + 0> is a big number`; // "100 is a big number!"
В примере выше мы вычислили значение number и соединили его в строку. Чтобы это сделать, мы сложили number + 0 . Благодаря этому JavaScript автоматически вызвал метод valueOf() на объекте number , чтобы вычислить его значение для операции сложения.
Мы можем переопределить метод valueOf() :
const number = new Number(100); number.valueOf = () => 99999; const newName = `$number + 0> is a big number`; // "99999 is a big number!"
Итог
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
- 130 курсов, 2000+ часов теории
- 1000 практических заданий в браузере
- 360 000 студентов
Наши выпускники работают в компаниях:
Откуда методы у типов number, string и boolean в JS?
Прочитав эту заметку, вы никогда не захотите писать на JavaScript. Но подождите! В работе эти проблемы не всплывают почти никогда. Я специально для вас залезу в этот тёмный подвал JS, чтобы поведать о том, как работает (255).toString(16) и (‘me: hello’).substr(4) и откуда у примитивных типов number, string и boolean методы?
Элегантный трюк JS
Как мы знаем, эти типы примитивные, то есть их значения — не объекты. Каждому «интересному» примитивному типу соответствует класс: Number, String и Boolean. Методы (toString, toLowerCase, substr) определены у этих типов-обёрток.
И каждый раз когда мы пытаемся получить свойство (или метод) примитивного значения, оно автоматически оборачивается в соответствующий объект, так что (‘x’).toLowerCase() превращается в (new String(‘x’)).toLowerCase() .
Такое оборачивание называется Boxing — положить в коробочку. После выполнения метода объект-обёртка просто выбрасывается.
Объекты типа Number — настоящие объекты, со всеми вытекающими бонусами и проблемами: new Number(3) !== new Number(3) , потому что теперь значения сравниваются по ссылке.
Никогда не используйте типы-обёртки с new как конструктор. Скорее всего, вы хотите именно значение примитивного типа: Number(‘3’) === 3 .
Попрактикуемся?
На собеседованиях мне несколько раз давали хитрую (но бесполезную) задачку. Что произойдет в результате выполнения этого кода:
const x = 10; x.hello = 'hello'; console.log(x.hello);Если вы знаете про Boxing, то всё должно быть понятно — попробуйте! Пишите ответы и вопросы в комментариях!
Что такое упаковка и распаковка(boxing/unboxing)?
Работа с объектами неизбежно влечет за собой накладные расходы по памяти и быстродействию. Чтобы избежать этого, используются переменные примитивных типов. Это, по сути, простые переменные, как в C или С++. byte занимает 1 байт памяти, int и float - по 4 байта, long и double - по 8 байт и т.д. В отличие от операций с объектами, операции с переменными примитивных типов не требуют выделения/освобождения памяти и выполняются быстро - они в в большинстве случаев в конечном счете компилируются в простые процессорные инструкции, что позволяет программам на Java часто работать со скоростью, сравнимой с программами, написанными на простых компилируемых (непосредственно в машинный код) языках (типа тех же C, С++).
Неудобство с ними в том, что с ними нельзя делать то, что можно делать со всеми объектами - они не имеют методов. Нельзя, например написать
int a = 5; ArrayList list = new ArrayList(); String s = a.toString(); // Ошибка list.add(a)) // Можно, но произойдет автоупаковка // и в коллекцию будет помещен Integerих нельзя помещать в коллекции и прочее.
Для того, чтобы обойти это неудобство, для всех примитивных типов существуют соответствующие классы-оболочки, объекты которых могут хранить значения примитивных типов, но обладает всеми свойствами нормальных объектов:
Integer a = 5; ArrayList list = new ArrayList(); String s = a.toString(); // OK list.add(a)) // OKСоздание объекта-оболочки из переменной примитивного типа называется упаковкой (boxing), а получение значения примитивного типа из объекта-оболочки -- распаковкой (unboxing). Объектам-оболочкам можно присваивать значения примитивных типов, а переменным примитивных типов - значения переменных-оболочек, при этом при необходимости автоматически создаются объекты-оболочки с соответствующими значениями (автоупаковка) или наоборот, примитивные значения извлекаются из оболочек (автораспаковка):
int a = 5; Integer b = 10; a = b; // OK, атораспаковка b = a * 123; // OK, автоупаковкаВ тех случаях, когда по контексту требуются объекты (присваивание, вызов метода с передачей параметров), а мы используем значения примитивных типов (переменные или выражения типа 2 * 3), всегда происходит автоупаковка.
Все объекты-оболочки - неизменяемые (immutable) типы, т.е. когда мы присваиваем им новое значение, фактически на замену прежнему объекту создается новый.
Отслеживать
13.7k 12 12 золотых знаков 43 43 серебряных знака 75 75 бронзовых знаков
ответ дан 28 окт 2016 в 13:47
3,387 10 10 серебряных знаков 24 24 бронзовых знакаВ версиях ниже 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 в следующих условиях:
- Когда значение примитивного типа передается в метод в качестве параметра метода, который ожидает объект соответствующего класса-оболочки.
- Когда значение примитивного типа присваивается переменной, соответствующего класса оболочки.
Отслеживать
25.1k 4 4 золотых знака 45 45 серебряных знаков 81 81 бронзовый знак
ответ дан 28 окт 2016 в 12:16
Константин Константин
198 8 8 бронзовых знаков
А int в String когда парсим это тоже упаковка?
28 окт 2016 в 12:52
@антонсорокин смотря как реализован метод. К примеру, String.valueOf() принимает int, поэтому никакого unboxing-а не будет.
28 окт 2016 в 12:57
@антонсорокин нет. Это преобразование только между примитивными типами и соответствующими типами-обёртками. Скажем, между int и Integer .
– user181100
28 окт 2016 в 13:02
Для понимания смысла упаковки-распаковки нужно понимать, как работает исполняемая среда и ваша программа на уровне процессора, а так же понимать, что термин ООП это всего лишь надстройка над классическим структурным программированием. Промежуточный байт-код во время выполнения программы преобразуется исполняемой средой в команды конкретного микропроцессора. Есть 3 основных способа хранения, обработки данных и их взаимодействия с программой: процессор-регистр, процессор-стек, и процессор-память. Команды работы с регистрами самые короткие и быстрые, со стеком - короткие, но выполняются на большее число тактов процессора, а команды работы с памятью - самые длинные и медленные. При этом в большинстве случаев простые типы данных хранятся и обрабатываются в регистрах процессора и в стеке, классы и более сложные типы данных хранятся в так называемой "куче" - динамической памяти, которая контролируется исполняемой средой. Механизм кучи достаточно сложен и работа с кучей по времени всегда дольше. Связка стек-регистры максимально эффективна при циклических алгоритмах, где в каждой итерации имеются длинные сложные вычисления, при этом циклический фрагмент кода с регистровой адресацией выполняется в сотни, а то и в десятки тысяч раз быстрее, чем если бы данные хранились в куче:) зато хранение данных в памяти упрощает программу и уменьшает ее размер, а также позволяет использовать все прелести ООП. Упаковку-распаковку придумали для того, чтобы избежать потери производительности программного обеспечения - если критически важна скорость - распаковываем свои данные в простой компактный формат, если скорость не критична - упаковываем в объектный тип и упрощаем программу. Кроме скорости возможна ситуация со значительными затратами ресурсов - к примеру, вам необходимо создать массив из миллиона однобайтовых элементов: что лучше использовать - примитивный однобайтовый тип или упакованный размером в несколько десятков байт?
Отслеживать
ответ дан 24 фев 2017 в 20:33
Андрей aka OberoN Андрей aka OberoN
- java
- java-faq
-
Важное на Мете
Связанные
Похожие
Подписаться на ленту
Лента вопроса
Для подписки на ленту скопируйте и вставьте эту ссылку в вашу программу для чтения RSS.
Дизайн сайта / логотип © 2023 Stack Exchange Inc; пользовательские материалы лицензированы в соответствии с CC BY-SA . rev 2023.11.15.1019
Нажимая «Принять все файлы cookie» вы соглашаетесь, что Stack Exchange может хранить файлы cookie на вашем устройстве и раскрывать информацию в соответствии с нашей Политикой в отношении файлов cookie.
JavaScript Boxing Wrappers
In JavaScript and other languages, primitives values don’t have methods or properties, so if you want to use them, you need to use a wrapper.
Primitives are the simplest elements of a programming language. JavaScript has six primitive types: string, Number, boolean, null, undefined, and symbol, and everything else are objects.
Index
- AutoBoxing
- Manual Boxing and Gotchas
- Unboxing
- Conclusion
AutoBoxing
Boxing is wrapping a primitive value in an Object. When you treat a primitive type like if it were an object, e.g., calling to the toLowerCase function, JavaScript would wrap the primitive type into the corresponding object. This new object is then linked to the related built-in , so you can use prototype methods on primitive types.
As you can see here when you try to use a method on a primitive value JavaScript automatically does the boxing, and in consequence, you can use the different methods of the String object:
//String primitive name
const name = "KESK";name.toLowerCase();//kesk
name.substring(1);//ESK
Auto-boxing is an essential feature because it enables ease-of-access to Standard Library methods.
Manual Boxing and Gotchas
In general, using the boxed object wrapper directly isn’t usually a good idea because there are some gotchas related to him, and you have to be careful if you don’t want unexpected results.
Consider the following examples:
const a = new Boolean(true)
if(a) console.log("it's true")// it's trueconst b = new Boolean(false)
if(!b) console.log("never runs");// objects are “truthy.“const c = Object(false)
if(!c) console.log("never runs"); // objects are “truthy.“
The problem here is that you are creating an object wrapper around a false value, but objects are “truthy.” So, if you want to box a primitive value manually, be careful.
