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

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

本文主要介绍Java中的自动拆箱与自动装箱的有关知识。

一、基本数据类型

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

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

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

字符类型char

布尔类型boolean

整数类型byteshortintlong

浮点数类型floatdouble

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

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

基本数据类型有什么好处

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

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

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

整型的取值范围

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

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

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

最小值:10000000 (-128)(-2^7)
最大值:01111111(127)(2^7-1)

整型的这几个类型中,

  • 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。

超出范围怎么办

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

int i = Integer.MAX_VALUE;
int j = Integer.MAX_VALUE;int k = i + j;
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之前,要进行装箱,可以通过以下代码:

Integer i = new Integer(10);

 

四、自动拆箱与自动装箱

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

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

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

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

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

 

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

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

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

public static  void main(String[]args){Integer integer=1; //装箱int i=integer; //拆箱
}

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

public static  void main(String[]args){Integer integer=Integer.valueOf(1); int i=integer.intValue(); 
}

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

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

 

六、哪些地方会自动拆装箱

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

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

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

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

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

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

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

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

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

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

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

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

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

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

场景三、包装类型的运算

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

Integer i = 10;
Integer j = 20;System.out.println(i+j);

反编译后代码如下:

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

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

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

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

boolean flag = true;
Integer i = 0;
int j = 1;
int k = flag ? i : j;

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

boolean flag = true;
Integer i = Integer.valueOf(0);
int j = 1;
int k = flag ? i.intValue() : j;

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

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

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

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

//自动拆箱
public int getNum1(Integer num) {return num;
}
//自动装箱
public Integer getNum2(int num) {return num;
}

 

七、自动拆装箱与缓存

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

public static void main(String... strings) {Integer integer1 = 3;Integer integer2 = 3;if (integer1 == integer2)System.out.println("integer1 == integer2");elseSystem.out.println("integer1 != integer2");Integer integer3 = 300;Integer integer4 = 300;if (integer3 == integer4)System.out.println("integer3 == integer4");elseSystem.out.println("integer3 != integer4");}

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

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

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

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

integer1 == integer2
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)‘\u0000’至 ‘\u007f’之间的字符(§3.10.4)

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

 

八、自动拆装箱带来的问题

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

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/325553.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

学习心得——张坤鹏

19级青鸟3班张坤鹏学习编程是很累的&#xff0c;需要不断地实践和探索&#xff1b;我的姑父就是学习计算机的&#xff0c;他说你选择了这个行业会很累&#xff0c;但只要你能入门&#xff0c;你就会了解到它的乐趣&#xff1b;我曾经见我姑父做一个项目&#xff0c;除了吃饭&am…

JS中的函数

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>04_函数</title> </head> <body> <!-- 1. 什么是函数?* 具有特定功能的n条语句的封装体* 只有函数是可执行的, 其它类型的数据是…

Android10创建文件Permission denied 失败

Android 10 创建文件一直报错&#xff1a;Permission denied&#xff1b; 原来 Android 10 改变了文件的存储方式 可以在Androidmainfest 里面的application添加 android:requestLegacyExternalStorage"true"

Microsoft发布.NET架构指南草案

微软开发部和Visual Studio产品团队提供了四份应用程序架构指南草案。这些草案涉及四个领域&#xff1a;微服务和Docker、ASP.NET Web应用程序、Azure云部署及Xamarin移动应用程序。每份指南包含一套符合相应主题的文档。微软希望可以获得整个社区对这些文档草案的反馈。 微服…

什么是线程安全,你真的了解吗

转载自 什么是线程安全&#xff0c;你真的了解吗 记得今年3月份刚来杭州面试的时候&#xff0c;有一家公司的技术总监问了我这样一个问题&#xff0c;说你给我说说有哪些线程安全的类&#xff0c;我心里一想&#xff0c;呵呵&#xff0c;这我早都背好了&#xff0c;稀里哗啦说…

学习心得——徐龙翥

19级青鸟3班 徐龙翥千里之行始于足下&#xff0c;不要认为html全是代码就认为学习起来特别艰难&#xff0c;如果你仔细观察的话就会发现&#xff0c;其实单词并没有多少&#xff0c;语法也并不复杂&#xff0c;只要做到上课认真听讲&#xff0c;课前预习以及课后复习&#xff0…

JS中的回调函数

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>05_回调函数</title> </head> <body> <button id"btn">测试点击事件</button> <!-- 1. 什么函数才是回调…

基于DotNet Core的RPC框架(一) DotBPE.RPC快速开始

0x00 简介 DotBPE.RPC是一款基于dotnet core编写的RPC框架&#xff0c;而它的爸爸DotBPE&#xff0c;目标是实现一个开箱即用的微服务框架&#xff0c;但是它还差点意思&#xff0c;还仅仅在构思和尝试的阶段。但不管怎么说RPC是微服务的基础&#xff0c;先来讲讲RPC的实现吧。…

android sharedpreferences工具类

package com.xt.deeptest5g.Utils;import android.content.Context; import android.content.SharedPreferences;/*** author : ZJS* e-mail : * date : 20-7-30下午7:46* desc : 用户账户信息保存工具类* version : 1.0*/ public class SharedPreferencesUtils {/*** 保存…

学习心得——刘文瑞

19级青鸟3班 刘文瑞以前因为经常玩游戏的原因&#xff0c;导致我对软件特别感兴趣&#xff0c;但是由于学习到的东西不多&#xff0c;经常是在玩游戏&#xff0c;真正能运用到计算机上的知识实在是少的可伶。像是代码之类的东西&#xff0c;以前只是听说过&#xff0c;但是一点…

教你如何定位及优化SQL语句的性能问题

转载自 教你如何定位及优化SQL语句的性能问题 在现如今的软件开发中&#xff0c;关系型数据库是做数据存储最重要的工具。无论是Oracale还是Mysql&#xff0c;都是需要通过SQL语句来和数据库进行交互的&#xff0c;这种交互我们通常称之为CRUD。在CRUD操作中&#xff0c;最最…

JS中的(IIFE)(立即调用函数)

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>06_IIEF</title> </head> <body> <!-- 1. 理解* 全称: Immediately-Invoked Function Expression 立即调用函数表达式* 别名: 匿…

SQL Server on Ubuntu——Ubuntu上的SQL Server(全截图)

本文从零开始一步一步介绍如何在Ubuntu上搭建SQL Server 2017&#xff0c;包括安装系统、安装SQL等相关步骤和方法&#xff08;仅供测试学习之用&#xff0c;基础篇&#xff09;。 一&#xff0e; 创建Ubuntu系统&#xff08;Create Ubuntu System&#xff09; 1. 前提准…

dp与px相互转换

package com.xt.deeptest5g.Utils;import android.content.res.Resources;/*** author : ZJS* e-mail : 15574564770163.com* date : 20-7-30下午9:02* desc : dp与px相互转换* version : 1.0*/ public class DensityUtil {public float density;public DensityUtil() {den…

学习心得——袁康凯

19级青鸟3班 袁康凯时光荏苒&#xff0c;不知不觉来济南信息工程学校已经二个多月&#xff0c;失去的很多&#xff0c;但收获的更多。失去很多以前嗤之以鼻却又丢弃不掉的坏毛病&#xff0c;认识了很多新同学新老师&#xff0c;学习了很多新知识。环境的巨大变化让我不断地改变…

面试必考的网络协议相关题目应该如何回答

转载自 面试必考的网络协议相关题目应该如何回答 最近&#xff0c;正处于校招季。很多小伙伴已经拿到了如愿以偿的Offer&#xff0c;有些小伙伴还在努力着。 平常&#xff0c;也会有一些粉丝会在公众号留言&#xff0c;或者在微信上问我一些面试题。其中有一类问题是除了Jav…

使用CSharp编写Google Protobuf插件

什么是 Google Protocol Buffer&#xff1f; Google Protocol Buffer( 简称 Protobuf) 是 Google 公司内部的混合语言数据标准&#xff0c;目前已经正在使用的有超过 48,162 种报文格式定义和超过 12,183 个 .proto 文件。他们用于 RPC 系统和持续数据存储系统。 Protocol Bu…

JS中关于能不能加分号

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>01_分号问题</title> </head> <body><!-- 1. js一条语句的后面可以不加分号 2. 是否加分号是编码风格问题, 没有应该不应该&…

浅析onWindowsFocusChanged()方法

浅析onWindowsFocusChanged()方法

jsp使用cookie实现记住用户名和密码

首先说一下实现的功能&#xff1a; 用户打开注册页面&#xff0c;最下面有个记住用户名和密码的复选框&#xff0c;如果勾选上&#xff0c;则在登录页面会自动将用户名和密码赋值到文本框中&#xff0c;使用java中的cookie实现&#xff0c;下面就是代码&#xff1a; 注册页面代…