原创

Java包装类以及拆箱、装箱详解

小郝不负流年
小郝不负流年   + 关注
2021-01-31 22:17:29   阅读5   评论0

基本数据类型

前面文章《Java基本数据类型以及取值范围》已经介绍了Java的基本数据类型有8种,它们的取值范围是固定的,不会随着机器硬件环境或者操作系统的改变而改变。

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

基本数据类型有什么好处

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

对于经常用到的类型,如 int 等,如果我们每次使用这种变量的时候都需要 new 一个 Java 对象的话,就会比较笨重。所以,和 C++ 一样,Java 提供了基本数据类型,这种数据的变量不需要使用 new 创建,他们不会在堆上创建,而是直接在栈内存中存储,因此会更加高效。

整型的取值范围

超出范围怎么办

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


  1. int i = Integer.MAX_VALUE;  
  2. int j = Integer.MAX_VALUE;  
  3.   
  4. int k = i + j;  
  5. System.out.println("i (" + i + ") + j (" + j + ") = k (" + k + ")");  

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

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

包装类

什么是包装类?

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

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

byteByte
booleanBoolean
shortShort
charCharacter
intInteger
longLong
floatFloat
doubleDouble

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

为什么需要包装类?

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

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

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

拆箱与装箱

什么是拆箱与装箱?

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

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

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


  1. Integer i = new Integer(10);  

自动拆箱与自动装箱

在 Java SE5 中,为了减少开发人员的工作,Java 提供了自动拆箱与自动装箱功能。

自动装箱:就是将基本数据类型自动转换成对应的包装类。

自动拆箱:就是将包装类自动转换成对应的基本数据类型。


  1. Integer i = 10;  //自动装箱  
  2. int b = i;     //自动拆箱  

Integer i=10 可以替代 Integer i = new Integer(10);,这就是因为 Java 帮我们提供了自动装箱的功能,不需要开发者手动去 new 一个 Integer 对象。

自动装箱与自动拆箱的实现原理

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

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


  1. publicstatic  void main(String[]args){  
  2.     Integer integer=1; //装箱  
  3.     int i=integer; //拆箱  
  4. }  

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


  1. publicstatic  void main(String[]args){  
  2.     Integer integer=Integer.valueOf(1);  
  3.     int i=integer.intValue();  
  4. }  

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

自动装箱都是通过包装类的 valueOf() 方法来实现的.自动拆箱都是通过包装类对象的 xxxValue() 来实现的。

哪些地方会自动拆装箱

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

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

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

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


  1. List li = new ArrayList<>();  
  2. for (int i = 1; i < 50; i ++){  
  3.     li.add(i);  
  4. }  

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


  1. List li = new ArrayList<>();  
  2. for (int i = 1; i < 50; i += 2){  
  3.     li.add(Integer.valueOf(i));  
  4. }  

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

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

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


  1. Integer a = 1;  
  2. System.out.println(a == 1 ? "等于" : "不等于");  
  3. Boolean bool = false;  
  4. System.out.println(bool ? "真" : "假");  

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


  1. Integer a = 1;  
  2. System.out.println(a.intValue() == 1 ? "等于" : "不等于");  
  3. Boolean bool = false;  
  4. System.out.println(bool.booleanValue ? "真" : "假");  

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

场景三、包装类型的运算

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


  1. Integer i = 10;  
  2. Integer j = 20;  
  3.   
  4. System.out.println(i+j);  
  1. 反编译后代码如下:



  1. Integer i = Integer.valueOf(10);  
  2. Integer j = Integer.valueOf(20);  
  3. System.out.println(i.intValue() + j.intValue());  

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


场景四、三目运算符的使用

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


  1. boolean flag = true;  
  2. Integer i = 0;  
  3. int j = 1;  
  4. int k = flag ? i : j;  

很多人不知道,其实在 int k = flag ? i : j; 这一行,会发生自动拆箱( JDK1.8 之前,详见:《阿里巴巴Java开发手册-泰山版》提到的三目运算符的空指针问题到底是个怎么回事? )。

反编译后代码如下:


  1. boolean flag = true;  
  2. Integer i = Integer.valueOf(0);  
  3. int j = 1;  
  4. int k = flag ? i.intValue() : j;  
  5. System.out.println(k);  

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

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

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

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


  1. //自动拆箱  
  2. public int getNum1(Integer num) {  
  3.  return num;  
  4. }  
  5. //自动装箱  
  6. public Integer getNum2(int num) {  
  7.  return num;  
  8. }  

自动拆装箱与缓存

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


  1. public static void main(String... strings) {  
  2.   
  3.     Integer integer1 = 3;  
  4.     Integer integer2 = 3;  
  5.   
  6.     if (integer1 == integer2)  
  7.         System.out.println("integer1 == integer2");  
  8.     else  
  9.         System.out.println("integer1 != integer2");  
  10.   
  11.     Integer integer3 = 300;  
  12.     Integer integer4 = 300;  
  13.   
  14.     if (integer3 == integer4)  
  15.         System.out.println("integer3 == integer4");  
  16.     else  
  17.         System.out.println("integer3 != integer4");  
  18. }  

我们普遍认为上面的两个判断的结果都是 false。虽然比较的值是相等的,但是由于比较的是对象,而对象的引用不一样,所以会认为两个 if 判断都是 false 的。在 Java 中,== 比较的是对象引用,而 equals 比较的是值。所以,在这个例子中,不同的对象有不同的引用,所以在进行比较的时候都将返回 false。奇怪的是,这里两个类似的 if 条件判断返回不同的布尔值。

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

  1. integer1 == integer2  
  2. integer3 != integer4  

原因就和 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 的值是:

  • -128 至 127 之间的整数 (§3.10.1)
  • true 和 false 的布尔值 (§3.10.3)
  • 至  之间的字符 (§3.10.4)

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

自动拆装箱带来的问题

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

  • 包装对象的数值比较,不能简单的使用 ==,虽然 -128 到 127 之间的数字可以,但是这个范围之外还是需要使用 equals 比较。
  • 前面提到,有些场景会进行自动拆装箱,同时也说过,由于自动拆箱,如果包装类对象为 null ,那么自动拆箱时就有可能抛出 NPE。
  • 如果一个 for 循环中有大量拆装箱操作,会浪费很多资源。

其他资料:

java自动拆装箱

对我有用,我要     转载  
文章分类: Java基础  
所属标签: 包装类   拆装箱  
  • 0条评论
  • 只看作者
  • 按时间|按热度
  • 由于本人多次涉及需要打印这个证明,而每次都会忘记入口,在网上各种搜索各种摸索很是浪费时间。故本次将操作流程整理记录下来,以备忘。同时也分享给大家。1、打开湖北政务服务网,地址:http://zwfw.hubei.gov.cn/s/index.html2、切换区域到“武汉市”3、在“特色服务”模块找到“(个人)武汉市社会保险公共服务平台”4、进入“(个人)武汉市社会保险公共服务平台”,登录账号密码<imgsrc="https://cdnstatic.hoscen.cn/blog/article/184053017752895488/img/497065960be44747825acb86a17483c1.png"style=
  • 目录:1、安装node.js环境2、安装cnpm3、安装vue-cli脚手架构建工具4、用vue-cli构建项目5、安装项目所需的依赖6、项目运行7、项目打包1、安装node.js环境下载地址:https://nodejs.org/zh-cn/安装过程没有太多好说的,安装完成后 win+R打开命令行输入node -v , 如图,出现版本号说明安装成功。npm包管理器是集成在node中的 , npm -v可以查看版本2、安装cnpm由于有些npm有些资源被屏蔽或者是国外资源的原因,经常会导致用npm安装依赖包的时
  • 1、找到nginx安装目录,找到nginx.conf ,  vi nginx.conf2、在http模块下面添加 server_tokens off;  ##隐藏版本号http {     ... 此处省略          server_tokens off;  ##隐藏版本号          ... 此处省略 }3、重启nginx4、效果如图,看不到版本号了
  • Thumbnailator,一个google开源的优秀的工具类 。根据提供的API可以快速实现图片缩放,区域裁剪,水印,旋转,保持比例 等操作。Thumbnailator官网:http://code.google.com/p/thumbnailator/ 本文主要讲图片原比例压缩功能。1、引入maven依赖2、demo测试public class TestImageUtil { public static void main(String[] args) throws IOException { String originImgPath = "C:UsersHoscenDesktopit.png"; String destImgPath = "C:UsersHoscenDesktopit-30.jpg"; Thumbnails.of(originImgPath) .scale(1f) .outputQuality(0.3f) .outputFormat("jpg") .toFile(destImgPath); } }3、测试结果原图,大小82.5kb<img src="https://static.hoscen.cn/blog/article/191329330033328128/img/5fa848b2b8f449bdb05dd78771777f83.png" style="width: 600px;" class="fr-fic fr-dib fr-fil
  • 一般我们在使用CDN时都设置有缓存时间,当源站资源发生变更后,如果缓存时间没到,那么用户访问的依旧是变更前的数据,虽说又拍云控制台提供了缓存刷新功能界面,但是每次都手动去刷新显示不太理想,当然又拍云也想到了这一点,提供给我们有API可以调用。本篇文章就是讲解如何接入又拍云缓存刷新API。网站免费接入又拍云CDN的方法,请查看我另外一篇文章,地址: https://www.hoscen.cn/blog/hao/articles/204022774975430656.html又拍云API文档:https://api.upyun.com/doc#/api/guide/overview看完文档,我们会知道又拍云提供有两个缓存刷新接口,一个支持通配符(但次数有限),一个是完整url刷新。同时注意调用接口时将 Token 放入 HTTP Header 中 。那么我们需要3个接口:1、获取token2、URL刷新3、缓存批量刷新详细请求参数和响应值请查看文档。话不多说,我们直接放上核心代码1、获取token2、URL刷新<img src="https://cdnstatic.hoscen.cn/blog/article/
  • 公益 404 页面介绍公益404页面是由腾讯公司员工志愿者自主发起的互联网公益活动。网站只需要在自己的404页面中嵌入一段简单的代码,就能通过互联网来迅速传播失踪儿童信息,从而提高找回失踪儿童的概率。失踪儿童信息来自宝贝回家寻子网。公益 404 页面接入方法:复制以下 js 代码,嵌入到您的 404 页面,可以自适应移动设备。<script type="text/javascript" src="//qzonestyle.gtimg.cn/qzone/hybrid/app/404/search_children.js" charset="utf-8"         homePageUrl="https://www.hoscen.cn/" homePageName="回到首页"></script>效果展示:注意事项:如果一个 404 页面的内容小于 512B,IE 会认为该 404 页面不够友好,在 IE 下将不会成功返回该 404 错误页面
  • 分享一个jQuery插件textarearesizer。它提供Resizer bar可拖动调整Textarea/div大小。代码示例:代码下载:    点击此处效果图:
  • XMind思维导图是做什么的,怎么使用,这些问题不在本次说明范围,本次就只做分享,一个XMind2020绿色免安装版分享!特点:1、无需安装,解压即用;2、可导出所有格式,不受限制;3、导出无水印;链接:https://pan.baidu.com/s/1jT0oXQS0Vxelsx1S2qVlRg 提取码:uew3 如果下载地址失效请留言反馈。资源来源朋友分享,仅供学习参考使用,请在下载后24小时内删除,请支持正版。
  • 前言:大部分前端开发Vue用的工具是vscode 或webstorm , 但是作为后端开发习惯了使用idea, 当然开发Vue也要用idea啦。前面已经讲到了《Vue入门篇:(一)环境搭建、项目创建、项目运行、项目打包》, 接下来就讲在idea开发vue项目的环境配置。配置过程:1、打开Setting > Plugins , 搜索vue.js插件并安装。2、打开Setting >Editor >  File Types , 配置HTML文件类型支持 .vue后缀3、打开Setting > Lnaguages .. > JavaScript  , 配置ES64、打开Terminal,执行npm r
  • 网站内测进行中,为了发现未知的可能性错误,然后可以改正它,得到更加友好的网站用户体验。 欢迎大家反馈问题,可以通过留言版或qq或邮箱反馈。邮箱:hoscen@qq.comQ Q:102287680