这并不是什么是装箱和拆箱的一个问题,这是相当为什么像Java和C#语言的需要是什么?
我非常熟悉wtih C ++,STL和升压。
在C ++中我可以很容易地写出这样的事情,
std::vector<double> dummy;
我有Java的一些经验,但我真的很惊讶,因为我不得不写这样的事情,
ArrayList<Double> dummy = new ArrayList<Double>();
我的问题,为什么它应该是一个对象,是什么这么难技术上谈论泛型时包括基本类型?
Answer 1:
什么是如此难以技术上谈论泛型时包括基本类型?
在Java的情况下,这是因为仿制药的方式工作。 在Java中,泛型是一个编译时招,阻止你把一个Image
对象到ArrayList<String>
。 然而,Java的泛型类型擦除实施:泛型类型信息在运行时丢失。 这是由于兼容性的原因,是因为仿制药中添加Java中的生活相当晚。 这意味着,运行时,一个ArrayList<String>
是一个有效的ArrayList<Object>
或更好:只是ArrayList
一个期望,并返回Object
在其所有的方法),其自动转换到String
,当你检索值。
但由于int
不从派生Object
,你不能把它放在预期(在运行时)一个ArrayList Object
,你可以不投的Object
来int
两种。 这意味着原始int
必须包装成型,它从继承Object
,像Integer
。
C#例如,工作方式不同。 在C#泛型还强迫在运行时和没有拳击需要具有List<int>
。 拳击在C#中,只有当你试图将一个值类型存储一样发生int
像一个引用类型变量object
。 由于int
在C#从继承Object
在C#,写object obj = 2
是完全有效的,然而,INT将盒装,这是由编译器自动完成(没有Integer
引用类型被暴露给用户或任何东西)。
Answer 2:
装箱和拆箱都是脱胎于该语言(如C#和Java)实现它们的内存分配策略的方式是必要的。
某些类型在堆中分配栈等上。 在为了治疗堆栈分配类型作为堆分配类型,拳击需要将栈上分配型移动到堆。 开箱是反向过程。
在C#堆栈分配的类型称为值类型 (例如System.Int32
和System.DateTime
)和堆分配类型称为引用类型 (例如System.Stream
和System.String
)。
在一些情况下,有利的是能够治疗值类型等的引用类型(反射是一个例子),但在大多数情况下,装箱和拆箱最好避免。
Answer 3:
我相信这也是因为原语不从Object继承。 假设你有希望能够在所有作为参数接受任何的方法,例如。
class Printer {
public void print(Object o) {
...
}
}
您可能需要一个简单的原始值传递给方法,如:
printer.print(5);
您将能够做,没有装箱/拆箱,因为5是一种原始的,而不是一个对象。 你可以重载的打印方法为每个原始类型,以使这样的功能,但它是一个痛苦。
Answer 4:
我只能告诉你的Java为什么它不支持primitve类型的仿制药。
首先有这个问题,以支持这个每次带来的讨论,如果用Java代码甚至应该有原始类型的问题。 这当然阻碍了实际问题的讨论。
第二个主要的原因不包括这是他们想要的二进制向后兼容性,因此它会在虚拟机上不知道仿制药的运行未经修改。 这种向后兼容/迁移兼容性原因,也是为什么现在集合API支持泛型和保持不变并没有(如当他们介绍泛型C#)一个完整的一套新的通用意识到收集API的。
兼容性是使用ersure(在编译时删除泛型类型参数信息),这也是你在Java中那么多的投选中警告的原因做了。
您仍然可以添加物化仿制药,但它不是那么容易的。 只需添加类型信息添加,而不是删除,因为它打破了源和二进制兼容(不能继续使用原始类型,你不能把它不会工作运行现有的已编译的代码,因为他们没有相应的方法)。
另一种方法是一个C#选择:见上
和自动自动装箱/拆箱是不支持这种使用情况,因为自动装箱的成本太大。
Java理论与实践:泛型陷阱
Answer 5:
在Java和C#(不像C ++)一切扩展对象,所以集合类如ArrayList可以容纳对象或任何其后代(基本上什么)的。
出于性能的考虑,但是,在Java或C#值类型的图元,被赋予了特殊的地位。 他们不反对。 你不能这样做(在Java中):
7.toString()
即使是的toString基于对象的方法。 为了弥合这一点头性能,创建了等效对象。 自动装箱移除具有把一个原语在其包装类,并再次将其取出,使代码更易读的样板代码。
在C#值类型和对象之间的区别是更灰。 见这里他们是如何不同。
Answer 6:
保存在堆上每个非阵列非字符串对象包含8或16字节的报头(尺寸六十四分之三十二位系统),接着该对象的公共和私有字段的内容。 数组和字符串具有上述头部,再加上一些更多的字节定义每个元素(和可能的维度数目,每个额外的维度的长度等)的阵列和尺寸的长度,随后所有的第一字段元素,则该第二等给予的对象的引用的所有字段,该系统可以很容易地检查报头,并确定它是什么类型。
引用类型的存储位置保持其唯一地标识存储在堆上的对象的四或八字节的值。 在本实施方式中,该值是一个指针,但它更容易(和语义上等同),以把它看作是一个“对象ID”。
价值型存储位置持有该值类型的字段的内容,但没有任何相关的头。 如果代码声明类型的变量Int32
,没有必要需要存储与信息Int32
说这是什么。 即该位置持有事实Int32
有效地存储作为计划的一部分,所以它不必存储在位置本身。 这一个代表,如果一个很大的节省,例如,一个有百万个对象,每个都具有类型的字段Int32
。 每个保持的对象Int32
具有其标识可以操作它的类的报头。 由于该类代码一个副本可以在任何的百万实例的操作,有一个事实,即场为一个Int32
是部分的代码是不是有对这些领域中的每一个存储更有效率包括它是什么信息。
当请求被制成一个值型存储位置的内容传递给代码不知道会发生该特定值类型拳击是必要的。 它期望未知类型的对象可以接受对存储在堆的对象的引用的代码。 由于存储在堆中的每个对象具有报头识别它是什么类型的对象,每当有必要在某种程度上这将需要知道它的类型使用对象代码可以使用该标头。
需要注意的是在.NET中,有可能宣布所谓的通用类和方法。 每一个这样的声明自动生成一个家族的其是除了堡他类型在其他们期望作用对象的相同类或方法。 如果一个传递一个Int32
到常规DoSomething<T>(T param)
将自动生成一个版本,其中类型的每个实例的程序的T
被有效地替换为Int32
。 常规的那个版本就会知道,每一个存储位置声明为类型T
持有Int32
,所以就像在一个例程硬编码到使用的情况下Int32
存储位置,就没有必要用这些存储类型信息位置本身。
文章来源: Why do some languages need Boxing and Unboxing?