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

时间:2020-03-05 18:40:03  来源:igfitidea点击:

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

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

解决方案

回答

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

例如,在Java中,如果我们想将其存储在Collection中,则可能需要将int值转换为Integer(装箱),因为原语不能存储在Collection中,只有对象。但是,当我们想将其从"集合"中取回时,我们可能希望将值作为" int"而不是" Integer"来获取,因此我们可以将其拆箱。

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

如今,最常见的讨论是在Java(和其他语言)的"自动装箱/自动拆箱"功能的上下文中进行的。这是以Java为中心的自动装箱说明。

回答

在.Net中:

通常,我们不能依赖于函数将使用什么类型的变量,因此我们需要使用一个对象变量,该变量从.Net中的最小公分母开始扩展,即"对象"。

但是,"对象"是一个类,并将其内容存储为引用。

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

虽然这两个都拥有相同的信息,但第二个列表更大且更慢。实际上,第二个列表中的每个值都是对包含" int"的" object"的引用。

之所以称为盒装,是因为" int"由" object"包装。当它返回时,int被取消装箱,转换回它的值。

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

对于引用类型(即所有"类")来说,这几乎没有什么问题,因为它们仍然存储为引用。

装箱的值类型的另一个问题是,要处理的是盒子而不是值,这不是很明显。当我们比较两个"结构"时,我们在比较值,但是当我们比较两个"类"时(默认情况下),我们在比较引用,即这些实例是否相同?

在处理盒装值类型时,这可能会造成混淆:

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

但是,在处理带框值时要特别注意另一件事。

回答

从C3.0概括地说:

Boxing is the act of casting a value
  type into a reference type:
int x = 9; 
object o = x; // boxing the int
unboxing is... the reverse:
// unboxing o
object o = 9; 
int x = (int)o;

回答

.NET FCL通用集合:

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

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

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

回答

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

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

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

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

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

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

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

回答

像其他任何东西一样,自动装箱如果使用不当,可能会出现问题。经典的方法是以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;
    }
}