一文读懂什么是Java中的自动拆装箱

作者 : 开心源码 本文共4022个字,预计阅读时间需要11分钟 发布时间: 2022-05-12 共125人阅读

前言

本文主要详情Java中的自动拆箱与自动装箱的有关知识。、

基本数据类型

基本类型,或者者叫做内置类型,是Java中不同于类(Class)的特殊类型。它们是我们编程中用最频繁的类型。

Java是一种强类型语言,第一次申明变量必需说明数据类型,第一次变量赋值称为变量的初始化。

Java基本类型共有八种,基本类型可以分为三类:

字符类型char

布尔类型boolean

整数类型byte、short、int、long

浮点数类型float、double。

Java中的数值类型不存在无符号的,它们的取值范围是固定的,不会随着机器硬件环境或者者操作系统的改变而改变。

实际上,Java中还存在另外一种基本类型void,它也有对应的包装类?java.lang.Void,不过我们无法直接对它们进行操作。

基本数据类型有什么好处

我们都知道在Java语言中,new一个对象是存储在堆里的,我们通过栈中的引使用来用这些对象;所以,对象本身来说是比较消耗资源的。

对于经常使用到的类型,如int等,假如我们每次用这种变量的时候都需要new一个Java对象的话,就会比较笨重。

所以,和C++一样,Java提供了基本数据类型,这种数据的变量不需要用new创立,他们不会在堆上创立,而是直接在栈内存中存储,因而会更加高效。

整型的取值范围

Java中的整型主要包含byte、short、int和long这四种,表示的数字范围也是从小到大的,之所以表示范围不同主要和他们存储数据时所占的字节数有关。

先来个简答的科普,1字节=8位(bit)。java中的整型属于有符号数。

先来看计算中8bit可以表示的数字:

整型的这几个类型中,

byte:byte使用1个字节来存储,范围为-128(-2^7)到127(2^7-1),在变量初始化的时候,byte类型的默认值为0。

short:short使用2个字节存储,范围为-32,768 (-2^15)到32,767 (2^15-1),在变量初始化的时候,short类型的默认值为0,一般情况下,由于Java本身转型的起因,可以直接写为0。

int:int使用4个字节存储,范围为-2,147,483,648 (-2^31)到2,147,483,647 (2^31-1),在变量初始化的时候,int类型的默认值为0。

long:long使用8个字节存储,范围为-9,223,372,036,854,775,808 (-2^63)到9,223,372,036, 854,775,807 (2^63-1),在变量初始化的时候,long类型的默认值为0L或者0l,也可直接写为0。

超出范围怎样办

上面说过了,整型中,每个类型都有肯定的表示范围,但是,在程序中有些计算会导致超出表示范围,即溢出。如以下代码:

输出结果:i (2147483647) + j (2147483647) = k (-2)

这就是发生了溢出,溢出的时候并不会抛异常,也没有任何提醒。所以,在程序中,用同类型的数据进行运算的时候,肯定要注意数据溢出的问题。

包装类型


Java语言是一个面向对象的语言,但是Java中的基本数据类型却是不面向对象的,这在实际用时存在很多的不便,为理解决这个不足,在设计类时为每个基本数据类型设计了一个对应的类进行代表,这样八个和基本数据类型对应的类统称为包装类(Wrapper Class)。

包装类均位于java.lang包,包装类和基本数据类型的对应关系如下表所示



在这八个类名中,除了Integer和Character类以后,其它六个类的类名和基本数据类型一致,只是类名的第一个字母大写就可。

为什么需要包装类

很多人会有疑问,既然Java中为了提高效率,提供了八种基本数据类型,为什么还要提供包装类呢?

这个问题,其实前面已经有了答案,由于Java是一种面向对象语言,很多地方都需要用对象而不是基本数据类型。比方,在集合类中,我们是无法将int 、double等类型放进去的。由于集合的容器要求元素是Object类型。

为了让基本类型也具备对象的特征,就出现了包装类型,它相当于将基本类型“包装起来”,使得它具备了对象的性质,并且为其增加了属性和方法,丰富了基本类型的操作。

拆箱与装箱

那么,有了基本数据类型和包装类,一定有些时候要在他们之间进行转换。比方把一个基本数据类型的int转换成一个包装类型的Integer对象。

我们认为包装类是对基本类型的包装,所以,把基本数据类型转换成包装类的过程就是打包装,英文对应于boxing,中文翻译为装箱。

反之,把包装类转换成基本数据类型的过程就是拆包装,英文对应于unboxing,中文翻译为拆箱。

在Java SE5之前,要进行装箱,可以通过以下代码:

自动拆箱与自动装箱

既然Java提供了自动拆装箱的能力,那么,我们就来看一下,究竟是什么原理,Java是如何实现的自动拆装箱功能。

我们有以下自动拆装箱的代码:

对以上代码进行反编译后可以得到以下代码:

从上面反编译后的代码可以看出,int的自动装箱都是通过Integer.valueOf()方法来实现的,Integer的自动拆箱都是通过integer.intValue来实现的。假如读者感兴趣,可以试着将八种类型都反编译一遍 ,你会发现以下规律:

哪些地方会自动拆装箱

我们理解过原理之后,在来看一下,什么情况下,Java会帮我们进行自动拆装箱。前面提到的变量的初始化和赋值的场景就不详情了,那是最简单的也最容易了解的。

我们主要来看一下,那些可能被忽略的场景。

场景一、将基本数据类型放入集合类

我们知道,Java中的集合类只能接收对象类型,那么以下代码为什么会不报错呢?

将上面代码进行反编译,可以得到以下代码:

以上,我们可以得出结论,当我们把基本数据类型放入集合类中的时候,会进行自动装箱。

场景二、包装类型和基本类型的大小比较

有没有人想过,当我们对Integer对象与基本类型进行大小比较的时候,实际上比较的是什么内容呢?看以下代码:

对以上代码进行反编译,得到以下代码:

可以看到,包装类与基本数据类型进行比较运算,是先将包装类进行拆箱成基本数据类型,而后进行比较的。

场景三、包装类型的运算

有没有人想过,当我们对Integer对象进行四则运算的时候,是如何进行的呢?看以下代码:

反编译后代码如下:

我们发现,两个包装类型之间的运算,会被自动拆箱成基本类型进行。

场景四、三目运算符的用

这是很多人不知道的一个场景,作者也是一次线上的血淋淋的Bug发生后才理解到的一种案例。看一个简单的三目运算符的代码:

很多人不知道,其实在int k = flag ? i : j;这一行,会发生自动拆箱。反编译后代码如下:

这其实是三目运算符的语法规范:当第二,第三位操作数分别为基本类型和对象时,其中的对象就会拆箱为基本类型进行操作。

由于例子中,flag ? i : j;片段中,第二段的i是一个包装类型的对象,而第三段的j是一个基本类型,所以会对包装类进行自动拆箱。假如这个时候i的值为null,那么久会发生NPE。(自动拆箱导致空指针异常)

场景五、函数参数与返回值

这个比较容易了解,直接上代码了:

自动拆装箱与缓存

Java SE的自动拆装箱还提供了一个和缓存有关的功能,我们先来看以下代码,猜测一下输出结果:

我们普遍认为上面的两个判断的结果都是false。尽管比较的值是相等的,但是因为比较的是对象,而对象的引使用不一样,所以会认为两个if判断都是false的。

在Java中,==比较的是对象应使用,而equals比较的是值。

所以,在这个例子中,不同的对象有不同的引使用,所以在进行比较的时候都将返回false。奇怪的是,这里两个相似的if条件判断返回不同的布尔值。

上面这段代码真正的输出结果:

起因就和Integer中的缓存机制有关。在Java 5中,在Integer的操作上引入了一个新功能来节省内存和提高性能。整型对象通过用相同的对象引使用实现了缓存和重使用。

适使用于整数值区间-128 至 +127。

只适使用于自动装箱。用构造函数创立对象不适使用。

具体的代码实现可以阅读Java中整型的缓存机制一文,这里不再阐述。

我们只要要知道,当需要进行自动装箱时,假如数字在-128至127之间时,会直接用缓存中的对象,而不是重新创立一个对象。

其中的javadoc详细的说明了缓存支持-128到127之间的自动装箱过程。最大值127可以通过-XX:AutoBoxCacheMax=size修改。

实际上这个功能在Java 5中引入的时候,范围是固定的-128 至 +127。后来在Java 6中,可以通过java.lang.Integer.IntegerCache.high设置最大值。

这使我们可以根据应使用程序的实际情况灵活地调整来提高性能。究竟是什么起因选择这个-128到127范围呢?由于这个范围的数字是最被广泛用的。 在程序中,第一次用Integer的时候也需要肯定的额外时间来初始化这个缓存。

在Boxing Conversion部分的Java语言规范(JLS)规定如下:

假如一个变量p的值是:

范围内的时,将p包装成a和b两个对象时,可以直接用a==b判断a和b的值能否相等。

自动拆装箱带来的问题

当然,自动拆装箱是一个很好的功能,大大节省了开发人员的精力,不再需要关心究竟什么时候需要拆装箱。但是,他也会引入少量问题。

包装对象的数值比较,不能简单的用==,尽管-128到127之间的数字可以,但是这个范围之外还是需要用equals比较。

前面提到,有些场景会进行自动拆装箱,同时也说过,因为自动拆箱,假如包装类对象为null,那么自动拆箱时就有可能抛出NPE。

假如一个for循环中有大量拆装箱操作,会白费很多资源。

欢迎工作一到五年的Java工程师朋友们加入Java架构开发:744677563

本群提供免费的学习指导 架构资料 以及免费的解答

不懂得问题都可以在本群提出来 之后还会有职业生涯规划以及面试指导

说明
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 一文读懂什么是Java中的自动拆装箱

发表回复