什么是拳击和拆箱,什么是折衷?
我正在寻找一个清晰,简洁和准确的答案。
作为实际答案,理想情况下,虽然链接到良好的解释欢迎。
盒装值是基本types *周围最小包装的数据结构 。 盒装值通常被存储为指向堆上对象的指针。
因此,boxed值使用更多的内存,并至less要访问两个内存查找:一次获取指针,另一个访问该指针原始指针。 显然这不是你内心循环中想要的那种东西。 另一方面,盒装值在系统中的其他types通常会更好。 由于它们是语言中的一stream数据结构,因此它们具有其他数据结构所具有的预期元数据和结构。
在Java和Haskell中,通用集合不能包含取消装箱的值。 .NET中的通用集合可以保留无装箱值,不会受到任何处罚。 在Java的generics只用于编译时types检查的情况下,.NET将为运行时实例化的每个genericstypes生成特定的类 。
Java和Haskell有unboxed数组,但它们明显不如其他集合方便。 然而,当需要高峰performance时,避免拳击和拆箱的开销会带来一些不便。
*对于这个讨论,原始值是可以存储在调用堆栈中的任何值,而不是作为指向堆中值的指针存储。 经常这只是机器types(整数,浮点数等),结构体,有时是静态大小的数组。 .NET-land调用它们的值types(与引用types相反)。 爪哇人称他们为原始types。 Haskellions只是叫他们拆箱。
**在这个答案中,我也专注于Java,Haskell和C#,因为这就是我所知道的。 对于什么是值得的,Python,Ruby和Javascript都有专门的盒装值。 这也被称为“一切都是对象”的方法***。
***注意:在某些情况下,足够先进的编译器/ JIT可以在运行时真正检测到在查看源代码时在语义上装箱的值,可以安全地作为unboxed值。 从本质上讲,多亏了辉煌的语言实现者,你的盒子有时是免费的。
来自C#3.0简而言之 :
拳击是将值types转换为引用types的行为:
int x = 9; object o = x; // boxing the int
拆箱是…相反:
// unboxing o object o = 9; int x = (int)o;
装箱和拆箱是将原始值转换为面向对象的包装类(装箱),或将值从面向对象的包装类转换回原始值(拆箱)的过程。
例如,在java中,如果要将它存储到Collection
,则可能需要将int
值转换为Integer
(装箱),因为基元不能存储在Collection
,只能是对象。 但是当你想把它从Collection
取出时,你可能想要把值作为一个int
而不是一个Integer
所以你可以解开它。
拳击和拆箱并不是天生不好 ,但它是一个折衷。 根据语言的实现,它可能比只使用基元更慢,内存更密集。 但是,它也可能允许您使用更高级别的数据结构,并在代码中实现更大的灵活性。
目前,Java(和其他语言)的“自动装箱/自动装箱”function是最常讨论的。 这是一个以Java为中心的自动装箱解释 。
在.Net中:
通常你不能依赖函数会消耗什么types的variables,所以你需要使用一个扩展自最小公分母的对象variables – 在.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
虽然这两个都持有相同的信息,第二个名单是更大,更慢。 第二个列表中的每个值实际上是对保存int
的object
的引用。
这被称为盒装因为int
是由object
包装。 当它返回时, int
被解除封装 – 转换回它的值。
对于值types(即所有structs
),这是缓慢的,并可能使用更多的空间。
对于引用types(即所有classes
),这是一个问题,因为它们无论如何都被存储为参考。
盒装值types的另一个问题是不清楚你是在处理盒子,而不是价值。 当你比较两个structs
然后你比较值,但是当你比较两个classes
然后(默认情况下)你比较参考 – 即这些是同一个实例吗?
处理盒装值types时可能会造成混淆:
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
然而,在处理盒装值时要小心另一件事。
.NET FCL通用集合:
List<T> Dictionary<TKey, UValue> SortedDictionary<TKey, UValue> Stack<T> Queue<T> LinkedList<T>
都是为了克服之前集合实现中的装箱和拆箱的性能问题而devise的。
有关更多信息,请参见第16章, CLR via C#(2nd Edition) 。
拳击是将值types转换为引用types的过程。
拆箱是将引用types转换为值types。
EX: int i=123; object o=i;// Boxing int j=(int)o;// UnBoxing
值types是:
int,char和结构,枚举。 引用types是:类,接口,数组,string和对象
装箱和取消装箱有助于将值types视为对象。 装箱意味着将一个值转换为对象引用types的一个实例。 例如, Int
是一个类, int
是一个数据types。 将int
转换为Int
是拳击的一个范例,而将Int
转换为int
则是取消装箱。 这个概念有助于垃圾收集,拆箱,另一方面,将对象types转换为值types。
int i=123; object o=(object)i; //Boxing o=123; i=(int)o; //Unboxing.
像其他任何东西一样,如果不仔细使用,自动装箱可能会有问题。 经典的结果是一个NullPointerException,不能跟踪它。 即使有一个debugging器。 尝试这个:
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; } }