Java:为啥需要包装类?

Posted

技术标签:

【中文标题】Java:为啥需要包装类?【英文标题】:Java: Why are wrapper classes needed?Java:为什么需要包装类? 【发布时间】:2011-01-09 05:44:38 【问题描述】:

在非常高的层次上,我知道我们需要通过使用它们各自的包装类来“包装”原始数据类型,例如 int 和 char,以便在 Java 集合中使用它们。我想了解 Java 集合如何在底层工作,问:“为什么我们需要将原始数据类型包装为对象才能在集合中使用它们?”我提前感谢您的帮助。

【问题讨论】:

Why are there wrapper classes in Java? 的可能重复项 【参考方案1】:

因为 Java 集合只能存储对象引用(因此您需要将原语装箱以将它们存储在集合中)。

阅读Autoboxing 上的这篇短文了解更多信息。

如果您想了解细节,可以归结为以下几点:

本地基元存储在堆栈中。集合通过对堆中对象内存位置的引用来存储它们的值。要获取本地原语的引用,您必须将值装箱(获取堆栈上的值并将其包装以存储在堆上)。

【讨论】:

【参考方案2】:

在虚拟机级别,这是因为与 java.lang.Object 及其派生类型等引用类型相比,原始类型在内存中的表示方式非常不同。例如,Java 中的原始 int 在内存中只有 4 个字节,而 Object 本身至少占用 8 个字节,另外还有 4 个字节用于引用它。这样的设计简单地反映了 CPU 可以更有效地处理原始类型这一事实。

因此,对“为什么需要包装器类型”的问题的一个答案是因为它可以提高性能。

但是对于程序员来说,这种区别会增加一些不希望的认知开销(例如,不能在集合中使用 int 和 float。)事实上,通过隐藏这种区别来进行语言设计是很有可能的——许多脚本语言都这样做这个,CLR 做那个。从 1.5 开始,Java 也这样做了。这是通过让编译器在原始表示和对象表示之间静默插入必要的转换(通常称为装箱/拆箱)来实现的。

因此,您的问题的另一个答案是,“不,我们不需要它”,因为编译器会自动为您执行此操作,并且在某种程度上您可以忘记幕后发生的事情。

【讨论】:

能否详细说明一下JVM是如何将原始类型和引用类型存储在内存中的? @Midnight Blue - 在此处阅读第一个答案(由 Jon Skeet 撰写):***.com/questions/2099695/…。它详细解释了事物是如何存储在 JVM 中以及何时存储的。 @Justin N. - 感谢您的链接 原始类型在内存中被表示为简单的值,几乎总是就像它们在 C 中的表示方式一样。例如,Java int 是 32 位整数,因此它占用 4 个字节。内存中的实际表示是特定于 CPU 的 --- 请参阅大端与小端。引用类型的表示是 JVM 特定的,但例如在 32 位 HotSpot 上,IIRC 前 4 个字节指的是“klass”数据结构(表示对象的类型),接下来的 4 个字节指的是方法调度表,以及实例字段如下。【参考方案3】:

阅读所有答案,但没有一个人能简单地用外行的话来解释它。

wrapper 类包装(包围)数据类型(可以是任何原始数据类型,例如 int、char、byte、long)并使其成为 object .

以下是需要包装类的几个原因:

    允许null 值。

    可用于ListMap等集合中

    可用于接受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
    

建议使用第二种创建包装类的方法,因为它不会创建新对象,因此占用的内存更少。

【讨论】:

【参考方案4】:

将原始类型值存储在 Collection 中。我们需要 Wrapper 类。

【讨论】:

【参考方案5】:

原始数据类型不能作为内存地址引用。这就是为什么我们需要包装器来充当原始值的占位符。然后可以对这些值进行变异和访问、重组、排序或随机化。

【讨论】:

您写道:“这些值可以被改变”。这实际上不适用于 Java 中的原始对象包装器。它们都是不可变的。 引用基本上是一个指针,只是限制性更强。在我看来,他们应该将其称为指针而不是引用,因为“引用”一词非常具有误导性。 此外,我们称其为引用变量。但是,实际上它是对变量(对象)的引用,具有讽刺意味的是,我们没有提到变量(对象)。所以它是对未命名变量(对象)的引用:) @helpermethod:我更喜欢“对象标识符”,但“堆对象引用”也可以。我不喜欢“指针”这个词,因为引用不像标准指针那样工作。如果从不访问普通指针,则其内容对任何代码执行都没有影响。在具有指针的语言中,代码持有指向不再存在的事物的指针是完全合法的,只要不尝试访问它们。相比之下,堆对象引用的存在可能会明显影响系统行为,无论代码是否访问它们,并且... ...在任何可访问但未识别有效对象的任何地方存在单个非空引用将导致 Java 和 .NET 中的即时系统崩溃。【参考方案6】:

集合使用泛型作为基础。收集框架旨在收集、存储和操作任何类的数据。所以它使用泛型类型。通过使用泛型,它能够存储您在其声明中指定名称的任何类的数据。

现在我们有各种场景,希望以与集合相同的方式存储原始数据。我们无法使用 ArrayList、HashSet 等 Collection 类来存储原始数据,因为 Collection 类只能存储对象。因此,为了在 Collection 中存储原始类型,我们提供了包装类。

编辑: 拥有包装类的另一个好处是没有对象可以被视为“无数据”。在原始的情况下,您将始终有一个值。

假设我们有方法签名

public void foo(String aString, int aNumber)

您不能在上述方法签名中将aNumber 设为可选。

但是如果你像这样签名:

public void foo(String aString, Integer aNumber) 您现在已将 aNumber 设为可选,因为用户可以将 null 作为值传递。

【讨论】:

【参考方案7】:

见Boxing and unboxing: when does it come up?

它适用于 C#,但同样的概念适用于 Java。约翰斯基特写下了答案。

【讨论】:

【参考方案8】:

嗯,原因是因为 Java 集合不区分原始和对象。它将它们全部作为对象处理,因此需要一个包装器。您可以轻松构建自己的不需要包装器的集合类,但最后,您必须为每种类型构建一个 char、int、float、double 等乘以集合的类型(Set、Map、列表,+ 他们的实现)。

你能想象那有多无聊吗?

事实上,不使用包装器带来的性能对于大多数应用程序来说几乎可以忽略不计。但是,如果您需要非常高的性能,也可以使用一些用于原始集合的库(例如 http://www.joda.org/joda-primitives/)

【讨论】:

集合区分得非常好:如果您尝试使用 java 原语,它们可以很好地处理对象并给您带来编译错误!【参考方案9】:

包装类提供了与相应数据类型相关的有用方法,您可以在某些情况下使用这些方法。

一个简单的例子。考虑一下,

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!

你能明白吗?

【讨论】:

【参考方案10】:

如果已知变量包含表示 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 中的实际类型需要在转换之前检查,而不是在执行转换之后。但是,如果变量持有对堆类型的引用与持有“任何东西”的变量之间没有区别,就无法避免开销比原本应该或应该的扩展得更远。

【讨论】:

【参考方案11】:

与 String 类非常相似,Wrappers 提供了附加功能,使程序员能够在数据存储过程中做更多事情。所以就像人们使用 String 类一样......

String uglyString = "fUbAr"; String myStr = uglyString.toLower();

他们也可以使用 Wrapper。类似的想法。

这是 Bharat 上面提到的集合/泛型的打字问题。

【讨论】:

【参考方案12】:

因为 int 不属于任何类。 我们将 datatype(int) 转换为 object(Interger)

【讨论】:

以上是关于Java:为啥需要包装类?的主要内容,如果未能解决你的问题,请参考以下文章

java包装类

Java工具类—包装类

JAVA学习笔记-包装类

Java学习笔记4.4.1 包装类 - 基本类型与包装类相互转换

为啥这个 C++ 包装类没有被内联?

Java基础知识(JAVA基本数据类型包装类)