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