【发布时间】:2011-01-09 05:44:38
【问题描述】:
在非常高的层次上,我知道我们需要通过使用它们各自的包装类来“包装”原始数据类型,例如 int 和 char,以便在 Java 集合中使用它们。我想了解 Java 集合如何在底层工作,问:“为什么我们需要将原始数据类型包装为对象才能在集合中使用它们?”我提前感谢您的帮助。
【问题讨论】:
标签: java collections
在非常高的层次上,我知道我们需要通过使用它们各自的包装类来“包装”原始数据类型,例如 int 和 char,以便在 Java 集合中使用它们。我想了解 Java 集合如何在底层工作,问:“为什么我们需要将原始数据类型包装为对象才能在集合中使用它们?”我提前感谢您的帮助。
【问题讨论】:
标签: java collections
因为 Java 集合只能存储对象引用(因此您需要将原语装箱以将它们存储在集合中)。
阅读Autoboxing 上的这篇短文了解更多信息。
如果您想了解细节,可以归结为以下几点:
本地基元存储在堆栈中。集合通过对堆中对象内存位置的引用来存储它们的值。要获取本地原语的引用,您必须将值装箱(获取堆栈上的值并将其包装以存储在堆上)。
【讨论】:
在虚拟机级别,这是因为与 java.lang.Object 及其派生类型等引用类型相比,原始类型在内存中的表示方式非常不同。例如,Java 中的原始 int 在内存中只有 4 个字节,而 Object 本身至少占用 8 个字节,另外还有 4 个字节用于引用它。这样的设计简单地反映了 CPU 可以更有效地处理原始类型这一事实。
因此,对“为什么需要包装器类型”的问题的一个答案是因为它可以提高性能。
但是对于程序员来说,这种区别会增加一些不希望的认知开销(例如,不能在集合中使用 int 和 float。)事实上,通过隐藏这种区别来进行语言设计是很有可能的——许多脚本语言都这样做这个,CLR 做那个。从 1.5 开始,Java 也这样做了。这是通过让编译器在原始表示和对象表示之间静默插入必要的转换(通常称为装箱/拆箱)来实现的。
因此,您的问题的另一个答案是,“不,我们不需要它”,因为编译器会自动为您执行此操作,并且在某种程度上您可以忘记幕后发生的事情。
【讨论】:
阅读所有答案,但没有一个人能简单地用外行的话来解释它。
wrapper 类包装(包围)数据类型(可以是任何原始数据类型,例如 int、char、byte、long)并使其成为 object .
以下是需要包装类的几个原因:
允许null 值。
可用于List、Map等集合中
可用于接受Object 类型参数的方法中。
可以像其他对象一样使用new ClassName()创建对象:
Integer wrapperInt = new Integer("10");
使Object类具有的所有功能可用,例如clone()、equals()、hashCode()、toString()等。
包装类可以通过两种方式创建:
使用构造函数:
Integer i = new Integer("1"); //new object is created
使用valueOf()静态方法:
Integer i = Integer.valueOf("100"); //100 is stored in variable
建议使用第二种创建包装类的方法,因为它不会创建新对象,因此占用的内存更少。
【讨论】:
将原始类型值存储在 Collection 中。我们需要 Wrapper 类。
【讨论】:
原始数据类型不能作为内存地址引用。这就是为什么我们需要包装器来充当原始值的占位符。然后可以对这些值进行变异和访问、重组、排序或随机化。
【讨论】:
集合使用泛型作为基础。收集框架旨在收集、存储和操作任何类的数据。所以它使用泛型类型。通过使用泛型,它能够存储您在其声明中指定名称的任何类的数据。
现在我们有各种场景,希望以与集合相同的方式存储原始数据。我们无法使用 ArrayList、HashSet 等 Collection 类来存储原始数据,因为 Collection 类只能存储对象。因此,为了在 Collection 中存储原始类型,我们提供了包装类。
编辑: 拥有包装类的另一个好处是没有对象可以被视为“无数据”。在原始的情况下,您将始终有一个值。
假设我们有方法签名
public void foo(String aString, int aNumber)
您不能在上述方法签名中将aNumber 设为可选。
但是如果你像这样签名:
public void foo(String aString, Integer aNumber)
您现在已将 aNumber 设为可选,因为用户可以将 null 作为值传递。
【讨论】:
见Boxing and unboxing: when does it come up?
它适用于 C#,但同样的概念适用于 Java。约翰斯基特写下了答案。
【讨论】:
嗯,原因是因为 Java 集合不区分原始和对象。它将它们全部作为对象处理,因此需要一个包装器。您可以轻松构建自己的不需要包装器的集合类,但最后,您必须为每种类型构建一个 char、int、float、double 等乘以集合的类型(Set、Map、列表,+ 他们的实现)。
你能想象那有多无聊吗?
事实上,不使用包装器带来的性能对于大多数应用程序来说几乎可以忽略不计。但是,如果您需要非常高的性能,也可以使用一些用于原始集合的库(例如 http://www.joda.org/joda-primitives/)
【讨论】:
包装类提供了与相应数据类型相关的有用方法,您可以在某些情况下使用这些方法。
一个简单的例子。考虑一下,
Integer x=new Integer(10);
//to get the byte value of 10
x.byteValue();
//but you can't do this,
int x=10;
x.byteValue(); //Wrong!
你能明白吗?
【讨论】:
如果已知变量包含表示 null 的特定位模式或可用于定位 Java 虚拟机对象头的信息,并且如果读取给定引用的对象头的方法将固有陷阱如果给定与null 关联的位模式,那么JVM 可以在假设存在一个变量的情况下访问由变量标识的对象。如果一个变量可以保存不是有效引用但不是特定的null 位模式的东西,那么任何尝试使用该变量的代码都必须首先检查它是否标识了一个对象。这会大大降低 JVM 的速度。
如果Object 派生自Anything,类对象派生自Object,但原语继承自另一个派生自Anything 的类,那么在64 位实现中说3/4 的可能位模式将表示低于 2^512 的 double 值,其中 1/8 表示在 +/- 1,152,921,504,606,846,975 范围内的 long 值,数十亿表示任何其他基元的任何可能值, 和 1/256 来识别物体。对Anything 类型的许多操作会比Object 类型慢,但这样的操作不会非常频繁;大多数代码最终会在尝试使用它之前将Anything 转换为更具体的类型;存储在Anything 中的实际类型需要在转换之前检查,而不是在执行转换之后。但是,如果变量持有对堆类型的引用与持有“任何东西”的变量之间没有区别,就无法避免开销比原本应该或应该的扩展得更远。
【讨论】:
与 String 类非常相似,Wrappers 提供了附加功能,使程序员能够在数据存储过程中做更多事情。所以就像人们使用 String 类一样......
String uglyString = "fUbAr";
String myStr = uglyString.toLower();
他们也可以使用 Wrapper。类似的想法。
这是 Bharat 上面提到的集合/泛型的打字问题。
【讨论】:
因为 int 不属于任何类。 我们将 datatype(int) 转换为 object(Interger)
【讨论】: