Что такое бокс и распаковка и каковы компромиссы?

135

Я ищу четкий, краткий и точный ответ.

В идеале как фактический ответ, хотя ссылки на хорошие объяснения приветствуются.

Кит
источник
2
Это действительно не зависит от языка?
Хенк Холтерман
3
@HenkHolterman это определенно не зависит от языка, хотя это также не относится ко всем языкам - например, это различие не будет иметь значения для большинства языков с динамической типизацией. Я не уверен, какой тег можно использовать вместо - language-but-not-type-agnostic? static-language-agnostic? Я не уверен, что ТАК нужно различие; может быть, хороший вопрос для мета, хотя.
Кит

Ответы:

189

Значения в штучной упаковке - это структуры данных, которые являются минимальными оболочками вокруг примитивных типов *. В штучной упаковке значения обычно хранятся в виде указателей на объекты в куче .

Таким образом, значения в штучной упаковке используют больше памяти и требуют как минимум два поиска в памяти: один - для получения указателя, а другой - для того, чтобы следовать этому указателю на примитив. Очевидно, это не та вещь, которую вы хотите в своих внутренних циклах. С другой стороны, значения в штучной упаковке обычно лучше работают с другими типами в системе. Так как они являются первоклассными структурами данных в языке, они имеют ожидаемые метаданные и структуру, которые имеют другие структуры данных.

В Java и Haskell универсальные коллекции не могут содержать распакованных значений. Родовые коллекции в .NET могут содержать распакованные значения без штрафов. В тех случаях, когда дженерики Java используются только для проверки типов во время компиляции, .NET будет генерировать определенные классы для каждого родового типа, создаваемого во время выполнения .

Java и Haskell имеют распакованные массивы, но они явно менее удобны, чем другие коллекции. Однако, когда требуется максимальная производительность, стоит немного неудобств, чтобы избежать накладных расходов на упаковку и распаковку.

* Для этого обсуждения примитивное значение - это любое, которое может быть сохранено в стеке вызовов , а не сохранено как указатель на значение в куче. Часто это просто типы машин (целые, плавающие и т. Д.), Структуры, а иногда и статические массивы. .NET-land называет их типами значений (в отличие от ссылочных типов). Java-люди называют их примитивными типами. Хаскеллионы просто называют их без коробки.

** В этом ответе я также фокусируюсь на Java, Haskell и C #, потому что это то, что я знаю. Для чего бы это ни стоило, Python, Ruby и Javascript имеют исключительно упакованные значения. Это также известно как подход «Все является объектом» ***.

*** Предостережение: достаточно продвинутый компилятор / JIT может в некоторых случаях фактически обнаружить, что значение, которое семантически упаковано при взгляде на источник, может безопасно быть распакованным значением во время выполнения. По сути, благодаря блестящим языковым разработчикам ваши ящики иногда свободны.

Питер Бернс
источник
Почему, хотя в штучной упаковке, какое преимущество дает CLR или что-то еще, получающее значения в форме бокса?
PositiveGuy
Короче говоря (ха-ха), это просто еще один объект, который очень удобен. Примитивы (по крайней мере, в Java) не происходят от Object, не могут иметь полей, не могут иметь методов и просто ведут себя совершенно иначе, чем другие типы значений. С другой стороны, работа с ними может быть очень быстрой и экономичной. Таким образом, компромисс.
Питер Бернс
2
Javascript имеет так называемые типизированные массивы (новый UInt32Array и т. Д.), Которые представляют собой массивы распакованных целочисленных значений и чисел с плавающей точкой.
nponeccop
126

из C # 3.0 В двух словах :

Бокс - это акт приведения типа значения в ссылочный тип:

int x = 9; 
object o = x; // boxing the int

распаковка это ... обратное

// unboxing o
object o = 9; 
int x = (int)o; 
Кристиан Хагелид
источник
72

Упаковка и распаковка - это процесс преобразования примитивного значения в объектно-ориентированный класс-оболочку (бокс) или преобразование значения из объектно-ориентированного класса-оболочки обратно в примитивное значение (распаковка).

Например, в Java вам может понадобиться преобразовать intзначение в Integer(бокс), если вы хотите сохранить его в, Collectionпотому что примитивы не могут быть сохранены Collectionтолько в объектах. Но когда вы хотите получить его обратно, Collectionвы можете получить значение как, intа не как, Integerчтобы распаковать его.

Бокс и распаковка - это не плохо , но это компромисс. В зависимости от языковой реализации, он может быть медленнее и занимать больше памяти, чем просто использование примитивов. Однако это также может позволить вам использовать структуры данных более высокого уровня и достичь большей гибкости в вашем коде.

В наши дни, это чаще всего обсуждается в контексте функции «autoboxing / autounboxing» Java (и других языков). Вот Java-ориентированное объяснение автобокса .

Джастин Стандарт
источник
23

В .Net:

Часто вы не можете полагаться на тип переменной, которую будет использовать функция, поэтому вам нужно использовать переменную объекта, которая начинается с наименьшего общего знаменателя - в .Net это object .

Однако objectявляется классом и хранит его содержимое в качестве ссылки.

List<int> notBoxed = new List<int> { 1, 2, 3 };
int i = notBoxed[1]; // this is the actual value

List<object> boxed = new List<object> { 1, 2, 3 };
int j = (int) boxed[1]; // this is an object that can be 'unboxed' to an int

Хотя оба они содержат одинаковую информацию, второй список больше и медленнее. Каждое значение во втором списке на самом деле является ссылкой objectнаint .

Это называется в штучной упаковке, потому что intобернут в object. Когда его отброситьint распаковывается, оно преобразуется обратно в его значение.

Для типов значений (т.е. всех structs ) это медленно и потенциально занимает гораздо больше места.

Для справочных типов (т.е. всех classes ) это гораздо меньше проблем, так как они все равно хранятся как ссылки.

Еще одна проблема с типом значений в штучной упаковке заключается в том, что не очевидно, что вы имеете дело с блоком, а не со значением. Когда вы сравниваете два, structsтогда вы сравниваете значения, но когда вы сравниваете два, classesтогда (по умолчанию) вы сравниваете ссылку - т.е. это один и тот же экземпляр?

Это может сбивать с толку при работе с коробочными типами значений:

int a = 7;
int b = 7;

if(a == b) // Evaluates to true, because a and b have the same value

object c = (object) 7;
object d = (object) 7;

if(c == d) // Evaluates to false, because c and d are different instances

Это легко обойти:

if(c.Equals(d)) // Evaluates to true because it calls the underlying int's equals

if(((int) c) == ((int) d)) // Evaluates to true once the values are cast

Однако при работе со значениями в штучной упаковке следует соблюдать осторожность.

Кит
источник
1
В vb.net различие между семантикой равенства более четкое, Objectне реализует оператор равенства, но типы классов можно сравнивать с Isоператором; и наоборот, Int32может использоваться с оператором равенства, но не Is. Это различие делает намного более ясным, какой тип сравнения делается.
суперкат
4

Boxingэто процесс преобразования типа значения в ссылочный тип. Принимая во внимание Unboxing, что преобразование ссылочного типа в тип значения.

EX: int i = 123;
    object o = i;// Boxing
    int j = (int)o;// UnBoxing

Типы значений являются: int, charи structures, enumerations. Ссылочные типы являются: Classes, interfaces, arrays, stringsиobjects

вани
источник
3

Общие коллекции .NET FCL:

List<T>
Dictionary<TKey, UValue>
SortedDictionary<TKey, UValue>
Stack<T>
Queue<T>
LinkedList<T>

все они были разработаны для преодоления проблем с производительностью упаковки и распаковки в предыдущих реализациях коллекции.

Для получения дополнительной информации см. Главу 16, CLR через C # (2-е издание) .

Джонатан Уэбб
источник
1

Упаковка и распаковка облегчает восприятие типов значений как объектов. Бокс означает преобразование значения в экземпляр ссылочного типа объекта. Например, Intявляется классом и intтипом данных. Преобразование intв Intявляется примером бокса, а преобразование Intв intраспаковку. Эта концепция помогает в сборке мусора. Распаковка, с другой стороны, преобразует тип объекта в тип значения.

int i=123;
object o=(object)i; //Boxing

o=123;
i=(int)o; //Unboxing.
Санджай Кумар
источник
В JavaScript var ii = 123; typeof ii возвращается number. var iiObj = new Number(123); typeof iiObjвозвращается object. typeof ii + iiObjвозвращается number. Так что это javascript-эквивалент бокса. Значение iiObj было автоматически преобразовано в примитивное число (без коробки), чтобы выполнить арифметику и вернуть значение без коробки.
PatS
-2

Как и все остальное, автобокс может быть проблематичным, если не использовать его осторожно. Классика заключается в том, чтобы получить исключение NullPointerException и не иметь возможности его отследить. Даже с отладчиком. Попробуй это:

public class TestAutoboxNPE
{
    public static void main(String[] args)
    {
        Integer i = null;

        // .. do some other stuff and forget to initialise i

        i = addOne(i);           // Whoa! NPE!
    }

    public static int addOne(int i)
    {
        return i + 1;
    }
}
PEELY
источник
Это просто плохой код, и он не имеет ничего общего с автобоксом. Переменная iпреждевременно инициализирована. Либо сделайте его пустым объявлением ( Integer i;), чтобы компилятор мог указать, что вы забыли его инициализировать, либо дождитесь его объявления, пока не узнаете его значение.
Эриксон
Хм, и если я сделаю что-то промежуточное внутри блока try catch, то компилятор заставит меня что-то инициализировать. Это не настоящий код - это пример того, как это может произойти.
PEELY
Что это демонстрирует? Нет абсолютно никаких причин использовать объект Integer. Вместо этого вам теперь приходится иметь дело с потенциальным NullPointer.
Ричард Клейтон