O que é boxe e unboxing e quais são as compensações?

135

Estou procurando uma resposta clara, concisa e precisa.

Idealmente, como a resposta real, embora os links para boas explicações sejam bem-vindos.

Keith
fonte
2
Isso é realmente independente de idioma?
Henk Holterman
3
@HenkHolterman certamente não é específico de um idioma, embora também não seja relevante para todos os idiomas - a distinção será irrelevante para a maioria dos idiomas de tipo dinâmico, por exemplo. Não tenho certeza de qual tag poderia ser usada language-but-not-type-agnostic. static-language-agnostic? Não tenho certeza de que o SO precise da distinção; pode ser uma boa pergunta para a meta.
29412 Keith

Respostas:

189

Valores em caixa são estruturas de dados que são invólucros mínimos em torno de tipos primitivos *. Os valores em caixa geralmente são armazenados como ponteiros para objetos na pilha .

Assim, os valores em caixa usam mais memória e levam no mínimo duas pesquisas de memória para acessar: uma para obter o ponteiro e outra para segui-lo até o primitivo. Obviamente, este não é o tipo de coisa que você deseja em seus circuitos internos. Por outro lado, os valores em caixa normalmente são melhores com outros tipos no sistema. Como são estruturas de dados de primeira classe no idioma, eles têm os metadados e a estrutura esperados que outras estruturas de dados possuem.

No Java e Haskell, as coleções genéricas não podem conter valores sem caixa. Coleções genéricas no .NET podem conter valores sem caixa, sem penalidades. Onde os genéricos de Java são usados ​​apenas para verificação de tipo em tempo de compilação, o .NET gera classes específicas para cada tipo genérico instanciado em tempo de execução .

Java e Haskell têm matrizes sem caixa, mas são claramente menos convenientes do que as outras coleções. No entanto, quando o desempenho máximo é necessário, vale a pena um pequeno inconveniente para evitar a sobrecarga do boxe e do unboxing.

* Para esta discussão, um valor primitivo é aquele que pode ser armazenado na pilha de chamadas , em vez de ser armazenado como um ponteiro para um valor no heap. Freqüentemente, são apenas os tipos de máquinas (entradas, flutuadores, etc.), estruturas e, às vezes, matrizes de tamanho estático. O .NET-land os chama de tipos de valor (em oposição aos tipos de referência). O pessoal de Java os chama de tipos primitivos. Os haskellions apenas os chamam de fora da caixa.

** Também estou focando em Java, Haskell e C # nesta resposta, porque é isso que eu sei. Pelo que vale, Python, Ruby e Javascript têm valores exclusivamente em caixa. Isso também é conhecido como a abordagem "Tudo é um objeto" ***.

*** Advertência: Em alguns casos, um compilador / JIT suficientemente avançado pode realmente detectar que um valor que é semanticamente encaixotado ao olhar a fonte pode ser com segurança um valor não encaixotado no tempo de execução. Em essência, graças aos implementadores de linguagem brilhantes, suas caixas às vezes são gratuitas.

Peter Burns
fonte
Por que, apesar de um valor em caixa, qual o benefício que o CLR ou o que obtém obtém valores de box?
PositiveGuy
Em resumo (ha ha), eles são apenas outro Objeto, o que é sempre muito conveniente. As primitivas (pelo menos em Java) não descendem de Object, não podem ter campos, não podem ter métodos e geralmente se comportam de maneira muito diferente de outros tipos de valores. Por outro lado, trabalhar com eles pode ser muito rápido e economiza espaço. Assim, o trade off.
Peter Burns,
2
O Javascript denominou matrizes digitadas (novo UInt32Array, etc), que são matrizes de entradas e flutuantes sem caixa.
Nspeccop
126

do C # 3.0 Em poucas palavras :

Boxe é o ato de converter um tipo de valor em um tipo de referência:

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

unboxing é ... o inverso:

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

Boxing & unboxing é o processo de converter um valor primitivo em uma classe de wrapper orientada a objetos (boxing) ou converter um valor de uma classe de wrapper orientada a objetos de volta ao valor primitivo (unboxing).

Por exemplo, em java, pode ser necessário converter um intvalor em um Integer(boxe) se você deseja armazená-lo em, Collectionporque os primitivos não podem ser armazenados em Collectionapenas objetos. Mas quando você deseja recuperá-lo, Collectionvocê pode obter o valor como um inte não um, Integerpara que você o desmarque.

Boxe e unboxing não são inerentemente ruins , mas são uma troca. Dependendo da implementação do idioma, ele pode ser mais lento e consome mais memória do que apenas o uso de primitivas. No entanto, também pode permitir que você use estruturas de dados de nível superior e obtenha maior flexibilidade no seu código.

Atualmente, é mais discutido no contexto do recurso "autoboxing / autounboxing" de Java (e de outra linguagem). Aqui está uma explicação centrada em java do autoboxing .

Justin Standard
fonte
23

Na rede:

Freqüentemente, você não pode confiar no tipo de variável que uma função consumirá; portanto, é necessário usar uma variável de objeto que se estende do menor denominador comum - em .Net, este é object.

No entanto, objecté uma classe e armazena seu conteúdo como referência.

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

Enquanto ambos mantêm a mesma informação, a segunda lista é maior e mais lenta. Cada valor na segunda lista é na verdade uma referência a um objectque contém o int.

Isso é chamado de caixa porque o inté envolvido pelo object. Quando ele é lançado fora da intcaixa - convertido de volta ao seu valor.

Para tipos de valor (isto é, todos structs), isso é lento e potencialmente usa muito mais espaço.

Para tipos de referência (isto é, todos classes), isso é muito menos problemático, pois eles são armazenados como referência de qualquer maneira.

Um outro problema com um tipo de valor em caixa é que não é óbvio que você está lidando com a caixa, e não com o valor. Quando você compara dois structs, você está comparando valores, mas quando você compara dois, classesentão (por padrão), você está comparando a referência - ou seja, essas são a mesma instância?

Isso pode ser confuso ao lidar com tipos de valor em caixa:

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

É fácil contornar:

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

No entanto, é outra coisa a ter cuidado ao lidar com valores em caixa.

Keith
fonte
1
No vb.net, a distinção entre semântica de igualdade é mais clara, Objectnão implementa o operador de igualdade, mas os tipos de classe podem ser comparados com o Isoperador; por outro lado, Int32pode ser usado com o operador de igualdade, mas não Is. Essa distinção torna muito mais claro que tipo de comparação está sendo feita.
Super12
4

Boxingé o processo de conversão de um tipo de valor em um tipo de referência. Considerando que Unboxingé a conversão de um tipo de referência em um tipo de valor.

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

Tipos de valor são: int, chare structures, enumerations. Tipos de referência são: Classes, interfaces, arrays, stringseobjects

vani
fonte
3

As coleções genéricas do FCL do .NET:

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

foram todos projetados para superar os problemas de desempenho do boxe e do unboxing nas implementações de coleções anteriores.

Para obter mais informações, consulte o capítulo 16, CLR via C # (2ª edição) .

Jonathan Webb
fonte
1

O boxe e o unboxing facilitam os tipos de valor a serem tratados como objetos. Boxe significa converter um valor em uma instância do tipo de referência do objeto. Por exemplo, Inté uma classe e inté um tipo de dados. A conversão intpara Inté uma exemplificação do boxe, enquanto a conversão Intpara inté unboxing. O conceito ajuda na coleta de lixo, o Unboxing, por outro lado, converte o tipo de objeto em tipo de valor.

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

o=123;
i=(int)o; //Unboxing.
Sanjay Kumar
fonte
Em javascript, var ii = 123; typeof ii retorna number. var iiObj = new Number(123); typeof iiObjretorna object. typeof ii + iiObjretorna number. Portanto, este é o equivalente javascript do boxe. O valor iiObj foi automaticamente convertido em um número primitivo (sem caixa) para executar a aritmética e retornar um valor sem caixa.
Pats
-2

Como qualquer outra coisa, a autoboxing pode ser problemática se não for usada com cuidado. O clássico é acabar com uma NullPointerException e não conseguir rastreá-la. Mesmo com um depurador. Tente o seguinte:

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
fonte
Este é apenas um código incorreto e não tem nada a ver com a caixa automática. A variável ié inicializada prematuramente. Faça uma declaração vazia ( Integer i;) para que o compilador possa indicar que você esqueceu de inicializá-lo ou aguarde para declará-lo até conhecer seu valor.
24620
Hmm, e se eu fizer algo dentro de um bloco try catch, o compilador me forçará a inicializá-lo com algo. Este não é um código real - é um exemplo de como isso poderia acontecer.
PEELY
O que isso demonstra? Não há absolutamente nenhuma razão para usar o objeto Inteiro. Em vez disso, agora você precisa lidar com um potencial NullPointer.
Richard Clayton