Co to jest boks i rozpakowywanie i jakie są kompromisy?

135

Szukam jasnej, zwięzłej i dokładnej odpowiedzi.

Idealnie jako rzeczywista odpowiedź, chociaż mile widziane linki do dobrych wyjaśnień.

Keith
źródło
2
Czy to naprawdę jest agnostyk językowy?
Henk Holterman
3
@HenkHolterman z pewnością nie jest specyficzny dla języka, chociaż nie dotyczy wszystkich języków - na przykład rozróżnienie to będzie nieistotne dla większości dynamicznie typowanych języków. Nie jestem pewien, jakiego tagu można użyć zamiast tego - language-but-not-type-agnostic? static-language-agnostic? Nie jestem pewien, czy SO potrzebuje rozróżnienia; może być jednak dobrym pytaniem do meta.
Keith

Odpowiedzi:

189

Wartości w pudełkach to struktury danych, które są minimalnymi opakowaniami wokół typów pierwotnych *. Wartości w pudełkach są zwykle przechowywane jako wskaźniki do obiektów na stercie .

Tak więc wartości pudełkowe zajmują więcej pamięci i wymagają co najmniej dwóch wyszukiwań pamięci, aby uzyskać dostęp: raz, aby uzyskać wskaźnik, a drugi, aby podążać za tym wskaźnikiem do prymitywu. Oczywiście nie jest to coś, czego chcesz w swoich wewnętrznych pętlach. Z drugiej strony, wartości pudełkowe zazwyczaj lepiej współgrają z innymi typami w systemie. Ponieważ są to struktury danych pierwszej klasy w języku, mają oczekiwane metadane i strukturę, które mają inne struktury danych.

W Javie i Haskell kolekcje ogólne nie mogą zawierać wartości bez opakowania. Kolekcje ogólne w .NET mogą przechowywać wartości bez opakowania bez żadnych kar. Tam, gdzie typy ogólne języka Java są używane tylko do sprawdzania typów w czasie kompilacji, .NET wygeneruje określone klasy dla każdego typu ogólnego, którego wystąpienie jest tworzone w czasie wykonywania .

Java i Haskell mają tablice rozpakowane, ale są one znacznie mniej wygodne niż inne kolekcje. Jednak gdy potrzebna jest maksymalna wydajność, warto trochę niedogodności, aby uniknąć narzutu związanego z pakowaniem i rozpakowywaniem.

* W tej dyskusji wartością pierwotną jest każda wartość, która może być przechowywana na stosie wywołań , a nie jako wskaźnik do wartości na stercie. Często są to tylko typy maszyn (int, zmiennoprzecinkowe itp.), Struktury, a czasem tablice o statycznych rozmiarach. .NET-land nazywa je typami wartości (w przeciwieństwie do typów referencyjnych). Ludzie z Javy nazywają je typami prymitywnymi. Haskellions nazywają je po prostu rozpakowanymi.

** W tej odpowiedzi skupiam się również na Javie, Haskellu i C #, ponieważ to wiem. Warto wiedzieć, że Python, Ruby i Javascript mają wyłącznie wartości w pudełkach. Jest to również znane jako podejście „Wszystko jest obiektem” ***.

*** Uwaga: wystarczająco zaawansowany kompilator / JIT może w niektórych przypadkach faktycznie wykryć, że wartość, która jest zapakowana semantycznie podczas przeglądania źródła, może bezpiecznie być wartością rozpakowaną w czasie wykonywania. Krótko mówiąc, dzięki genialnym implementatorom języka, twoje skrzynki są czasami darmowe.

Peter Burns
źródło
Dlaczego, mimo wartości pudełkowej, jaką korzyść daje CLR lub cokolwiek innego z wartości pudełkowych?
PositiveGuy
Krótko mówiąc (ha ha), to tylko kolejny obiekt, który jest zawsze tak wygodny. Prymitywy (przynajmniej w Javie) nie pochodzą od Object, nie mogą mieć pól, nie mogą mieć metod i po prostu ogólnie zachowują się zupełnie inaczej niż inne typy wartości. Z drugiej strony praca z nimi może być bardzo szybka i zajmująca mało miejsca. Tak więc kompromis.
Peter Burns
2
Javascript ma tak zwane tablice typowane (nowe UInt32Array itp.), Które są tablicami niepakowanych liczb int i floatów.
nponeccop
126

z C # 3.0 w pigułce :

Boks to czynność rzutowania typu wartości na typ referencyjny:

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

rozpakowywanie jest ... odwrotnie:

// unboxing o
object o = 9; 
int x = (int)o; 
Christian Hagelid
źródło
72

Pakowanie i rozpakowywanie to proces konwertowania wartości pierwotnej na klasę opakowującą zorientowaną obiektowo (opakowanie) lub przekształcania wartości z klasy opakowującej zorientowanej obiektowo z powrotem na wartość pierwotną (rozpakowywanie).

Na przykład w Javie może być konieczne przekonwertowanie intwartości na Integer(pudełko), jeśli chcesz ją przechowywać w a, Collectionponieważ prymitywy nie mogą być przechowywane w a Collection, tylko w obiektach. Ale kiedy chcesz odzyskać to z powrotem, Collectionmożesz chcieć uzyskać wartość jako an, inta nie an, Integerwięc powinieneś ją rozpakować.

Boks i rozpakowywanie nie jest z natury złe , ale jest to kompromis. W zależności od implementacji języka może być wolniejszy i bardziej obciążający pamięć niż zwykłe używanie prymitywów. Jednak może również pozwolić na użycie struktur danych wyższego poziomu i osiągnięcie większej elastyczności w kodzie.

Obecnie jest to najczęściej omawiane w kontekście funkcji „autoboxing / autounboxing” Javy (i innych języków). Oto wyjaśnienie autoboxingu skoncentrowane na Javie .

Justin Standard
źródło
23

W sieci:

Często nie można polegać na tym, jaki typ zmiennej będzie zużywać funkcja, więc trzeba użyć zmiennej obiektu, która rozciąga się od najniższego wspólnego mianownika - to jest .Net object.

Jednak objectjest to klasa i przechowuje swoją zawartość jako odniesienie.

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

Chociaż oba zawierają te same informacje, druga lista jest dłuższa i wolniejsza. Każda wartość na drugiej liście jest w rzeczywistości odwołaniem do elementu objectzawierającego rozszerzenie int.

Nazywa się to pudełkiem, ponieważ intjest opakowane przez object. Kiedy intzostanie odrzucony, zostanie rozpakowany - przekonwertowany z powrotem na swoją wartość.

W przypadku typów wartości (tj. Wszystkich structs) jest to powolne i potencjalnie zajmuje dużo więcej miejsca.

W przypadku typów referencyjnych (tj. Wszystkich classes) jest to znacznie mniejszy problem, ponieważ i tak są one przechowywane jako referencje.

Kolejnym problemem związanym z typem wartości w pudełku jest to, że nie jest oczywiste, że masz do czynienia z pudełkiem, a nie z wartością. Kiedy porównujesz dwie structs, porównujesz wartości, ale kiedy porównujesz dwie, classeswtedy (domyślnie) porównujesz odniesienie - tj. Czy to jest ta sama instancja?

Może to być mylące, gdy mamy do czynienia z opakowanymi typami wartości:

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

Łatwo jest obejść:

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

Jednak w przypadku wartości pudełkowych należy zachować ostrożność.

Keith
źródło
1
W vb.net rozróżnienie między semantyką równości jest wyraźniejsze, Objectnie implementuje operatora równości, ale typy klas można porównać z Isoperatorem; odwrotnie, Int32może być używany z operatorem równości, ale nie Is. To rozróżnienie sprawia, że ​​dużo jaśniej jest, jakiego rodzaju porównania dokonuje się.
supercat
4

Boxingjest procesem konwersji typu wartości na typ referencyjny. Natomiast Unboxingjest to konwersja typu referencyjnego na typ wartościowy.

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

Typy wartości to: int, chari structures, enumerations. Typy referencyjne są następujące: Classes, interfaces, arrays, stringsiobjects

vani
źródło
3

Kolekcje ogólne .NET FCL:

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

wszystkie zostały zaprojektowane, aby przezwyciężyć problemy z wydajnością pakowania i rozpakowywania w poprzednich implementacjach kolekcji.

Aby uzyskać więcej informacji, zobacz rozdział 16, CLR przez C # (wydanie drugie) .

Jonathan Webb
źródło
1

Pakowanie i rozpakowywanie ułatwia traktowanie typów wartości jako obiektów. Boks oznacza konwersję wartości na wystąpienie typu odwołania do obiektu. Na przykład Intjest klasą i intjest typem danych. Konwersja intna Intjest przykładem boksu, podczas gdy konwersja Intna intjest rozpakowywaniem. Koncepcja pomaga w wyrzucaniu elementów bezużytecznych, z drugiej strony Unboxing konwertuje typ obiektu na typ wartości.

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

o=123;
i=(int)o; //Unboxing.
Sanjay Kumar
źródło
W javascript var ii = 123; typeof ii zwraca number. var iiObj = new Number(123); typeof iiObjzwraca object. typeof ii + iiObjzwraca number. To jest odpowiednik boksu w javascript. Wartość iiObj została automatycznie przekonwertowana na liczbę pierwotną (bez opakowania) w celu wykonania arytmetyki i zwrócenia wartości bez opakowania.
PatS
-2

Jak wszystko inne, autoboxing może być problematyczny, jeśli nie jest używany ostrożnie. Klasyczne jest to, że kończy się wyjątek NullPointerException i nie można go wyśledzić. Nawet z debugerem. Spróbuj tego:

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;
    }
}
SKÓRA
źródło
To po prostu zły kod i nie ma nic wspólnego z autoboxingiem. Zmienna izostała przedwcześnie zainicjalizowana. Albo uczyń ją pustą deklaracją ( Integer i;), aby kompilator mógł wskazać, że zapomniałeś jej zainicjować, albo poczekaj z zadeklarowaniem, aż poznasz jej wartość.
erickson
Hmm, a jeśli zrobię coś pomiędzy wewnątrz bloku try catch, kompilator zmusi mnie do zainicjowania go czymś. To nie jest prawdziwy kod - to przykład tego, jak to się mogło stać.
PEELY
Co to pokazuje? Nie ma absolutnie żadnego powodu, aby używać obiektu Integer. Zamiast tego masz teraz do czynienia z potencjalnym NullPointer.
Richard Clayton