什么是拳击和拆箱以及有什么权衡取舍?

时间:2008-08-16 08:34:25

标签: language-agnostic boxing glossary unboxing

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

理想情况下,作为实际答案,尽管欢迎链接到好的解释。

8 个答案:

答案 0 :(得分:180)

盒装值为data structures,它们是primitive types *周围的最小包装器。盒装值通常存储为the heap上对象的指针。

因此,盒装值使用更多内存并至少需要两次内存查找才能访问:一次获取指针,另一次跟随指向该基元的指针。显然,这不是你想要在内循环中的那种东西。另一方面,盒装值通常与系统中的其他类型相比更好。由于它们是该语言中的一流数据结构,因此它们具有其他数据结构所具有的预期元数据和结构。

在Java和Haskell中,泛型集合不能包含未装箱的值。 .NET中的通用集合可以保留未装箱的值而不会受到惩罚。 Java的泛型仅用于编译时类型检查,.NET将generate specific classes for each generic type instantiated at run time

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

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

**我也在这个答案中专注于Java,Haskell和C#,因为这就是我所知道的。对于它的价值,Python,Ruby和Javascript都只有盒装价值。这也被称为“一切都是对象”的方法***。

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

答案 1 :(得分:122)

来自C# 3.0 In a Nutshell

  

拳击是铸造价值的行为   输入引用类型:

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

拆箱是......相反的:

// unboxing o
object o = 9; 
int x = (int)o; 

答案 2 :(得分:68)

拳击&拆箱是将原始值转换为面向对象的包装类(装箱),或将面向对象的包装类中的值转换回原始值(拆箱)的过程。

例如,在java中,如果要将int存储在Integer中,则可能需要将Collection值转换为Collection(拳击),因为基元不能存储在Collection中的唯一对象。但是,如果您希望将其从int中取回,则可能希望将值设为Integer而不是{{1}},以便将其取消装箱。

拳击和拆箱本身并不是,但这是一种权衡。根据语言实现,它可能比仅使用基元更慢且占用更多内存。但是,它也可能允许您使用更高级别的数据结构,并在代码中实现更大的灵活性。

现在,最常见的是在Java(和其他语言)“autoboxing / autounboxing”功能的上下文中讨论。这是java centric explanation of autoboxing

答案 3 :(得分: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的{​​{1}}的引用。

这称为盒装,因为intint包裹。当它退回时,object被取消装箱 - 转换回它的值。

对于值类型(即所有int),这很慢,并且可能会占用更多空间。

对于参考类型(即所有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

然而,在处理盒装值时要小心另一件事。

答案 4 :(得分:3)

.NET FCL泛型集合:

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

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

有关详情,请参阅第16章CLR via C# (2nd Edition)

答案 5 :(得分:2)

拳击是将值类型转换为引用类型的过程。

取消装箱是将参考类型转换为值类型。

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

价值类型是:
int,char和结构,枚举。 参考类型是: 类,接口,数组,字符串和对象

答案 6 :(得分:1)

装箱和拆箱有助于将值类型视为对象。拳击意味着将值转换为对象引用类型的实例。例如,Int是一个类,int是一种数据类型。将int转换为Int是拳击的示例,而将Int转换为int则是取消装箱。这个概念有助于垃圾收集,另一方面,拆箱,将对象类型转换为值类型。

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

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

答案 7 :(得分:-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;
    }
}