Qu'est-ce que la boxe et le déballage et quels sont les compromis?

135

Je recherche une réponse claire, concise et précise.

Idéalement comme réponse réelle, bien que les liens vers de bonnes explications soient les bienvenus.

Keith
la source
2
Est-ce vraiment indépendant de la langue?
Henk Holterman
3
@HenkHolterman ce n'est certainement pas spécifique à une langue, bien que ce ne soit pas non plus pertinent pour toutes les langues - la distinction ne sera pas pertinente pour la plupart des langues typées dynamiquement, par exemple. Je ne sais pas quelle balise pourrait être utilisée à la place - language-but-not-type-agnostic? static-language-agnostic? Je ne suis pas sûr que SO ait besoin de la distinction; pourrait être une bonne question pour meta cependant.
Keith

Réponses:

189

Les valeurs encadrées sont des structures de données qui sont des wrappers minimaux autour des types primitifs *. Les valeurs encadrées sont généralement stockées sous forme de pointeurs vers des objets sur le tas .

Ainsi, les valeurs encadrées utilisent plus de mémoire et nécessitent au minimum deux recherches de mémoire pour y accéder: une fois pour obtenir le pointeur et une autre pour suivre ce pointeur vers la primitive. De toute évidence, ce n'est pas le genre de chose que vous voulez dans vos boucles intérieures. D'un autre côté, les valeurs encadrées fonctionnent généralement mieux avec les autres types du système. Puisqu'il s'agit de structures de données de première classe dans le langage, elles ont les métadonnées et la structure attendues des autres structures de données.

Dans Java et Haskell, les collections génériques ne peuvent pas contenir de valeurs sans boîte. Les collections génériques dans .NET peuvent contenir des valeurs sans boîte sans pénalités. Lorsque les génériques de Java sont uniquement utilisés pour la vérification de type au moment de la compilation, .NET générera des classes spécifiques pour chaque type générique instancié au moment de l'exécution .

Java et Haskell ont des tableaux sans boîte, mais ils sont nettement moins pratiques que les autres collections. Cependant, lorsque des performances de pointe sont nécessaires, cela vaut un petit inconvénient pour éviter les frais généraux liés à la boxe et au déballage.

* Pour cette discussion, une valeur primitive est toute valeur qui peut être stockée sur la pile d'appels , plutôt que stockée sous forme de pointeur vers une valeur sur le tas. Souvent, il ne s'agit que des types de machine (ints, floats, etc.), des structures et parfois des tableaux de taille statique. .NET-land les appelle des types valeur (par opposition aux types référence). Les gens de Java les appellent des types primitifs. Les Haskellions les appellent simplement sans boîte.

** Je me concentre également sur Java, Haskell et C # dans cette réponse, car c'est ce que je sais. Pour ce que ça vaut, Python, Ruby et Javascript ont tous des valeurs exclusivement encadrées. Ceci est également connu sous le nom d'approche «Tout est un objet» ***.

*** Mise en garde: un compilateur / JIT suffisamment avancé peut dans certains cas détecter qu'une valeur qui est encadrée sémantiquement lors de la consultation de la source, peut en toute sécurité être une valeur sans boîte au moment de l'exécution. En substance, grâce à de brillants implémenteurs de langage, vos boîtes sont parfois gratuites.

Peter Burns
la source
Pourquoi si une valeur encadrée, quel avantage le CLR ou quoi que ce soit obtient-il des valeurs de boxe?
PositiveGuy
En bref (ha ha), ils ne sont qu'un autre objet, ce qui est très pratique. Les primitives (au moins en Java) ne descendent pas d'Object, ne peuvent pas avoir de champs, ne peuvent pas avoir de méthodes et se comportent généralement très différemment des autres types de valeurs. D'un autre côté, travailler avec eux peut être très rapide et peu encombrant. Ainsi le compromis.
Peter Burns
2
Javascript a ce que l'on appelle des tableaux typés (nouveau UInt32Array, etc.) qui sont des tableaux d'entiers et de flottants non boxés.
nponeccop
126

à partir de C # 3.0 En bref :

La boxe est l'acte de transtyper un type valeur en type référence:

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

le déballage est ... l'inverse:

// unboxing o
object o = 9; 
int x = (int)o; 
Christian Hagelid
la source
72

Le boxing & unboxing est le processus de conversion d'une valeur primitive en une classe wrapper orientée objet (boxing), ou de conversion d'une valeur d'une classe wrapper orientée objet vers la valeur primitive (unboxing).

Par exemple, en java, vous devrez peut-être convertir une intvaleur en un Integer(boxing) si vous souhaitez la stocker dans un Collectioncar les primitives ne peuvent pas être stockées dans un Collection, uniquement des objets. Mais lorsque vous souhaitez le récupérer, Collectionvous voudrez peut-être obtenir la valeur en tant que an intet non pas Integerpour le déballer.

La boxe et le déballage ne sont pas intrinsèquement mauvais , mais c'est un compromis. Selon l'implémentation du langage, cela peut être plus lent et plus gourmand en mémoire que la simple utilisation de primitives. Cependant, cela peut également vous permettre d'utiliser des structures de données de niveau supérieur et d'obtenir une plus grande flexibilité dans votre code.

Ces jours-ci, il est le plus souvent discuté dans le contexte de la fonctionnalité "autoboxing / autounboxing" de Java (et d'autres langages). Voici une explication java centrée de l'autoboxing .

Justin Standard
la source
23

Dans .Net:

Souvent, vous ne pouvez pas vous fier au type de variable qu'une fonction consommera, vous devez donc utiliser une variable objet qui s'étend du plus petit dénominateur commun - dans .Net c'est object.

Cependant objectest une classe et stocke son contenu comme référence.

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

Bien que les deux contiennent les mêmes informations, la deuxième liste est plus longue et plus lente. Chaque valeur de la deuxième liste est en fait une référence à un objectqui contient le int.

Ceci est appelé boxed car le intest enveloppé par le object. Quand il intest renvoyé, il est déballé - reconverti à sa valeur.

Pour les types valeur (c'est-à-dire tous structs), cela est lent et utilise potentiellement beaucoup plus d'espace.

Pour les types de référence (c'est-à-dire tous classes), c'est bien moins un problème, car ils sont de toute façon stockés comme référence.

Un autre problème avec un type de valeur encadré est qu'il n'est pas évident que vous traitez avec la boîte plutôt qu'avec la valeur. Lorsque vous comparez deux, structsvous comparez des valeurs, mais lorsque vous comparez deux, vous comparez classes(par défaut) la référence - c'est-à-dire que ce sont les mêmes instances?

Cela peut être déroutant lorsqu'il s'agit de types de valeur encadrés:

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

Il est facile de contourner:

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

Cependant, c'est une autre chose à laquelle il faut faire attention lorsqu'il s'agit de valeurs encadrées.

Keith
la source
1
Dans vb.net, la distinction entre la sémantique d'égalité est plus claire, Objectn'implémente pas l'opérateur d'égalité, mais les types de classe peuvent être comparés à l' Isopérateur; inversement, Int32peut être utilisé avec l'opérateur d'égalité, mais pas Is. Cette distinction rend beaucoup plus clair le type de comparaison effectué.
supercat du
4

Boxingest le processus de conversion d'un type valeur en type référence. Alors que Unboxingc'est la conversion d'un type de référence en un type de valeur.

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

Types de valeur sont: int, charet structures, enumerations. Types de référence sont: Classes, interfaces, arrays, stringsetobjects

vani
la source
3

Les collections génériques .NET FCL:

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

ont tous été conçus pour surmonter les problèmes de performances de la boxe et du déballage dans les implémentations de collection précédentes.

Pour plus d'informations, consultez le chapitre 16, CLR via C # (2e édition) .

Jonathan Webb
la source
1

La boxe et le déballage facilitent le traitement des types de valeurs comme des objets. Boxing signifie convertir une valeur en une instance du type de référence d'objet. Par exemple, Intest une classe et intest un type de données. La conversion inten Intest une illustration de la boxe, alors que la conversion Inten intest un déballage. Le concept aide dans le garbage collection, Unboxing, d'autre part, convertit le type d'objet en type valeur.

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

o=123;
i=(int)o; //Unboxing.
Sanjay Kumar
la source
En javascript, var ii = 123; typeof ii renvoie number. var iiObj = new Number(123); typeof iiObjrevient object. typeof ii + iiObjrevient number. C'est donc l'équivalent javascript de la boxe. La valeur iiObj a été automatiquement convertie en un nombre primitif (sans boîte) afin d'effectuer l'arithmétique et renvoyer une valeur sans boîte.
PatS
-2

Comme toute autre chose, la boxe automatique peut être problématique si elle n'est pas utilisée avec précaution. Le classique est de se retrouver avec une NullPointerException et de ne pas pouvoir la retrouver. Même avec un débogueur. Essaye ça:

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
la source
C'est juste un mauvais code et cela n'a rien à voir avec l'auto-box. La variable iest initialisée prématurément. Soit faites-en une déclaration vide ( Integer i;) pour que le compilateur puisse signaler que vous avez oublié de l'initialiser, soit attendez de la déclarer jusqu'à ce que vous connaissiez sa valeur.
erickson
Hmm, et si je fais quelque chose entre les deux dans un bloc try catch, le compilateur me forcera à l'initialiser avec quelque chose. Ce n'est pas un vrai code - c'est un exemple de la façon dont cela pourrait arriver.
PEELY
Qu'est-ce que cela démontre? Il n'y a absolument aucune raison d'utiliser l'objet Integer. Au lieu de cela, vous devez maintenant faire face à un NullPointer potentiel.
Richard Clayton