Що таке бокс та unboxing та які розпродажі?


135

Я шукаю чітку, коротку та точну відповідь.

Ідеально як реальна відповідь, хоча посилання на хороші пояснення вітаються.


2
Це справді мова-агностик?
Хенк Холтерман

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

Відповіді:


189

Значення в коробці - це структури даних, які є мінімальними обгортками навколо примітивних типів *. Значення в коробці зазвичай зберігаються як покажчики на об’єкти на купі .

Таким чином, значки в коробці використовують більше пам’яті та займають як мінімум два пошуку пам’яті для доступу: один раз для отримання вказівника, а інший для того, щоб слідувати за цим вказівником на примітив. Очевидно, це не та річ, яку ви хочете у своїх внутрішніх петлях. З іншого боку, значення в коробці, як правило, краще грають з іншими типами в системі. Оскільки вони є першокласними структурами даних у мові, вони мають очікувані метадані та структуру, які мають інші структури даних.

У загальних колекціях Java та Haskell не можуть міститись незменені значення. Загальні колекції в .NET можуть містити незмінені значення без штрафних санкцій. Якщо дженерики Java використовуються лише для перевірки часу компіляції, .NET буде генерувати конкретні класи для кожного загального типу, інстанційованого під час виконання .

У Java та Haskell є безкомпонентні масиви, але вони явно менш зручні, ніж інші колекції. Однак, коли потрібні пікові показники, варто уникнути невеликих незручностей, щоб уникнути накладних витрат на бокс та розпакування.

* Для цього обговорення примітивне значення - це будь-яке, яке може бути збережене в стеку викликів , а не зберігатися як вказівник на значення на купі. Часто це лише типи машин (ints, floats тощо), структури та іноді масиви статичного розміру. .NET-land називає їх типовими значеннями (на відміну від референтних типів). Ява називають їх примітивними типами. Haskellions просто називають їх некомплектними.

** У цій відповіді я також зосереджуюсь на Java, Haskell та C #, тому що це я знаю. Для чого це варто, Python, Ruby та Javascript мають виключно коробкові значення. Це також відомо як підхід "Все - це об'єкт" ***.

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


Чому хоч значення в коробці, яку користь отримує CLR або що інше отримує значення боксу?
PositiveGuy

Якщо коротко (ха-ха), вони просто ще один Об'єкт, який завжди такий зручний. Примітиви (принаймні на Java) не сходять з Object, не можуть мати поля, не можуть мати методів, а просто, як правило, ведуть себе дуже інакше, ніж інші типи значень. З іншого боку, робота з ними може бути дуже швидкою та просторовою. Тим самим торг.
Пітер Бернс

2
Javascript має так звані типізовані масиви (новий UInt32Array тощо), які є масивами незмешених вбудованих та плаваючих елементів.
nponeccop


72

Boxing & unboxing - це процес перетворення примітивного значення в об'єктно-орієнтований клас обгортки (бокс) або перетворення значення з об'єктно-орієнтованого класу обгортки назад у примітивне значення (unboxing).

Наприклад, у java вам може знадобитися перетворити intзначення в Integer(бокс), якщо ви хочете зберегти його в, Collectionтому що примітиви не можуть зберігатися Collectionлише в об'єктах. Але коли ви хочете отримати його назад з Collectionвас, ви можете отримати значення як а, intа не Integerтак, то ви розблокуєте його.

Бокс і розпакування не є по суті поганим , але це компроміс. Залежно від мовної реалізації, вона може бути повільнішою та інтенсивнішою, ніж просто використання примітивів. Однак це може також дозволяти вам використовувати структури даних більш високого рівня та досягати більшої гнучкості у вашому коді.

У наші дні це найчастіше обговорюється в контексті функції "автобоксингу / автобоксингу" Java (та іншої мови). Ось java центричне пояснення автобоксингу .


23

В .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це unboxed - перетворюється назад у його значення.

Для типів значень (тобто всіх 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. Це розрізнення значно зрозуміліше, який тип порівняння робиться.
supercat

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- розпакування. Концепція допомагає у збиранні сміття, Unboxing, з іншого боку, перетворює тип об'єкта у тип значення.

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;
    }
}

Це просто поганий код і не має нічого спільного з автобоксінгом. Змінна iпередчасно ініціалізується. Або зробіть це порожнім декларацією ( Integer i;), щоб компілятор міг вказати, що ви забули його ініціалізувати, або чекайте, щоб оголосити його, поки не дізнаєтесь його значення.
erickson

Хм, і якщо я щось роблю між блоком спробу попадання, тоді компілятор змусить мене щось ініціалізувати. Це не справжній код - це приклад того, як це могло статися.
PEELY

Що це демонструє? Немає абсолютно ніяких причин використовувати об’єкт Integer. Натомість вам зараз доведеться мати справу з потенційним NullPointer.
Річард Клейтон
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.