什么是装箱和拆箱,需要进行哪些取舍?


135

我正在寻找一个清晰,简洁和准确的答案。

理想情况下,作为实际答案,尽管欢迎提供指向良好解释的链接。


2
这真的与语言无关吗?
Henk Holterman

3
@HenkHolterman当然不是特定于语言的,尽管它也不与所有语言相关-例如,对于大多数动态类型的语言而言,这种区别是不相关的。我不确定可以使用哪个标签代替language-but-not-type-agnosticstatic-language-agnostic?我不确定SO是否需要区分。对于meta来说可能是个好问题。
基思2012年

Answers:


189

装箱的值是对原始类型 * 进行最小包装的数据结构。装箱的值通常存储为指向堆上对象的指针。

因此,盒装值使用更多的内存,并且至少要进行两次内存查找才能访问:一次获取指针,另一次跟随该指针指向原语。显然,这不是您想要在内部循环中执行的操作。另一方面,带框值通常在系统中的其他类型上发挥更好的作用。由于它们是语言中的一流数据结构,因此它们具有其他数据结构所期望的元数据和结构。

在Java和Haskell中,通用集合不能包含未装箱的值。.NET中的泛型集合可以保存未装箱的值,而不会受到惩罚。在Java的泛型仅用于编译时类型检查的情况下,.NET将为在运行时实例化的每种泛型类型生成特定的类

Java和Haskell具有未装箱的数组,但是它们明显不如其他集合方便。但是,当需要最佳性能时,避免装箱和拆箱的开销值得一点不便。

*在此讨论中,原始值是可以存储在调用堆栈中的任何值,而不是作为指向堆上值的指针存储的值。通常只是机器类型(整数,浮点数等),结构,有时还有静态大小的数组。.NET-land将它们称为值类型(与引用类型相对)。Java人士称它们为原始类型。Haskellions只是称它们为未装箱。

**在此答案中,我还将重点放在Java,Haskell和C#上,因为这就是我所知道的。值得一提的是,Python,Ruby和Javascript都具有专门装箱的值。这也称为“一切都是对象”方法***。

***警告:足够高级的编译器/ JIT在某些情况下实际上可以检测到在查看源代码时在语义上装箱的值可以在运行时安全地成为未装箱的值。本质上,由于出色的语言实现者,您的盒子有时是免费的。


为什么使用盒装值,CLR或其他东西有什么好处呢?
PositiveGuy 2010年

简而言之(哈哈),它们只是另一个对象,非常方便。(至少在Java中)基元不是从Object派生的,不能具有字段,不能具有方法,并且通常在行为上与其他类型的值完全不同。另一方面,与他们合作可以非常快速且节省空间。因此权衡。
彼得·伯恩斯

2
JavaScript具有所谓的类型化数组(新的UInt32Array等),它们是未装箱的int和float数组。
nponeccop 2012年


72

装箱和拆箱是将原始值转换为面向对象的包装器类(装箱),或者将值从面向对象的包装器类转换回原始值(装箱)的过程。

例如,在Java中,如果要将intInteger存储在中,则可能需要将值转换为(装箱),Collection因为原语不能存储在Collection只能对象中。但是,当您想将其取回时,Collection您可能希望将其值设为an int而不是an,Integer因此可以将其拆箱。

装箱和拆箱并不是天生的坏事,但这是一个折衷。取决于语言的实现,它可能比仅使用基元更慢且占用更多内存。但是,它也可以使您使用更高级别的数据结构并在代码中获得更大的灵活性。

如今,最常在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

之所以将其称为盒装(Boxed),int是因为包在中object。当其后退时,int将取消装箱-转换回其值。

对于值类型(即all structs),这很慢,并且可能占用更多空间。

对于引用类型(即all 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 2012年

4

Boxing是将值类型转换为引用类型的过程。而Unboxing引用类型是值类型的转换。

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

值类型:intcharstructuresenumerations。引用类型: Classesinterfacesarraysstringsobjects


3

.NET FCL通用集合:

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

旨在克服以前的集合实现中装箱和拆箱的性能问题。

有关更多信息,请参见第16章,通过C#(第二版)进行CLR


1

装箱和拆箱有助于将值类型视为对象。装箱意味着将值转换为对象引用类型的实例。例如,Int是一个类,int是一个数据类型。转换intInt被拳击的例证,而转换Intint是拆箱。该概念有助于垃圾回收,另一方面,拆箱将对象类型转换为值类型。

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

o=123;
i=(int)o; //Unboxing.

在javascript中,var ii = 123; typeof ii 返回numbervar iiObj = new Number(123); typeof iiObj返回objecttypeof 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

嗯,如果我在try catch块之间进行了某些操作,则编译器将迫使我使用某些内容对其进行初始化。这不是真正的代码-这是如何发生的示例。
PEELY

这说明了什么?绝对没有理由使用Integer对象。相反,您现在必须处理潜在的NullPointer。
理查德·克莱顿
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.