javaee论坛

普通会员

225648

帖子

352

回复

366

积分

楼主
发表于 2019-11-01 06:18:01 | 查看: 68 | 回复: 1

六年前,我从苏州回到洛阳,抱着一幅“海归”的心态,投了不少简历,也“约谈”了不少面试官,但仅有两三个令我感到满意。其中有一位叫老马,至今还活在我的手机通讯录里。他当时扔了一个面试题把我砸懵了:说说基本类型和包装类型的区别吧。

我当时二十三岁,正值青春年华,从事Java编程已有N年经验(N<4),自认为所有的面试题都能对答如流,结果没想到啊,被“刁难”了——原来洛阳这块互联网的荒漠也有技术专家啊。现在回想起来,脸上不自觉地泛起了羞愧的红晕:主要是自己当时太菜了。不管怎么说,是时候写篇文章剖析一下基本类型和包装类型的区别了。

Java的每个基本类型都对应了一个包装类型,比如说int的包装类型为Integer,double的包装类型为Double。基本类型和包装类型的区别主要有以下4点。

01、包装类型可以为null,而基本类型不可以

别小看这一点区别,它使得包装类型可以应用于POJO中,而基本类型则不行。

POJO是什么呢?这里稍微说明一下。

POJO的英文全称是PlainOrdinaryJavaObject,翻译一下就是,简单无规则的Java对象,只有属性字段以及setter和getter方法,示例如下。

classWriter{privateIntegerage;privateStringname;publicIntegergetAge(){returnage;}publicvoidsetAge(Integerage){this.age=age;}publicStringgetName(){returnname;}publicvoidsetName(Stringname){this.name=name;}}

和POJO类似的,还有数据传输对象DTO(DataTransferObject,泛指用于展示层与服务层之间的数据传输对象)、视图对象VO(ViewObject,把某个页面的数据封装起来)、持久化对象PO(PersistantObject,可以看成是与数据库中的表映射的Java对象)。

那为什么POJO的属性必须要用包装类型呢?

《阿里巴巴Java开发手册》上有详细的说明,我们来大声朗读一下(预备,起)。

数据库的查询结果可能是null,如果使用基本类型的话,因为要自动拆箱(将包装类型转为基本类型,比如说把Integer对象转换成int值),就会抛出NullPointerException的异常。

02、包装类型可用于泛型,而基本类型不可以

泛型不能使用基本类型,因为使用基本类型时会编译出错。

List<int>list=newArrayList<>();//提示Syntaxerror,insert"Dimensions"tocompleteReferenceTypeList<Integer>list=newArrayList<>();

为什么呢?因为泛型在编译时会进行类型擦除,最后只保留原始类型,而原始类型只能是Object类及其子类——基本类型是个特例。

03、基本类型比包装类型更高效

基本类型在栈中直接存储的具体数值,而包装类型则存储的是堆中的引用。

很显然,相比较于基本类型而言,包装类型需要占用更多的内存空间。假如没有基本类型的话,对于数值这类经常使用到的数据来说,每次都要通过new一个包装类型就显得非常笨重。

03、两个包装类型的值可以相同,但却不相等

两个包装类型的值可以相同,但却不相等——这句话怎么理解呢?来看一段代码就明明白白了。

Integerchenmo=newInteger(10);Integerwanger=newInteger(10);System.out.println(chenmo==wanger);//falseSystem.out.println(chenmo.equals(wanger));//true

两个包装类型在使用“”进行判断的时候,判断的是其指向的地址是否相等。chenmo和wanger两个变量使用了new关键字,导致它们在“”的时候输出了false。

而chenmo.equals(wanger)的输出结果为true,是因为equals方法内部比较的是两个int值是否相等。源码如下。

privatefinalintvalue;publicintintValue(){returnvalue;}publicbooleanequals(Objectobj){if(objinstanceofInteger){returnvalue==((Integer)obj).intValue();}returnfalse;}

瞧,虽然chenmo和wanger的值都是10,但他们并不相等。换句话说就是:将“==”操作符应用于包装类型比较的时候,其结果很可能会和预期的不符。

04、自动装箱和自动拆箱

既然有了基本类型和包装类型,肯定有些时候要在它们之间进行转换。把基本类型转换成包装类型的过程叫做装箱(boxing)。反之,把包装类型转换成基本类型的过程叫做拆箱(unboxing)。

在JavaSE5之前,开发人员要手动进行装拆箱,比如说:

Integerchenmo=newInteger(10);//手动装箱intwanger=chenmo.intValue();//手动拆箱

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

Integerchenmo=10;//自动装箱intwanger=chenmo;//自动拆箱

上面这段代码使用JAD反编译后的结果如下所示:

Integerchenmo=Integer.valueOf(10);intwanger=chenmo.intValue();

也就是说,自动装箱是通过Integer.valueOf()完成的;自动拆箱是通过Integer.intValue()完成的。理解了原理之后,我们再来看一道老马当年给我出的面试题。

//1)基本类型和包装类型inta=100;Integerb=100;System.out.println(a==b);//2)两个包装类型Integerc=100;Integerd=100;System.out.println(c==d);//3)c=200;d=200;System.out.println(c==d);

答案是什么呢?有举手要回答的吗?答对的奖励一朵小红花哦。

第一段代码,基本类型和包装类型进行==比较,这时候b会自动拆箱,直接和a比较值,所以结果为true。

第二段代码,两个包装类型都被赋值为了100,这时候会进行自动装箱,那==的结果会是什么呢?

我们之前的结论是:将“==”操作符应用于包装类型比较的时候,其结果很可能会和预期的不符。那结果是false?但这次的结果却是true,是不是感觉很意外?

第三段代码,两个包装类型重新被赋值为了200,这时候仍然会进行自动装箱,那==的结果会是什么呢?

吃了第二段代码的亏后,是不是有点怀疑人生了,这次结果是true还是false呢?扔个硬币吧,哈哈。我先告诉你结果吧,false。

为什么?为什么?为什么呢?

事情到了这一步,必须使出杀手锏了——分析源码吧。

之前我们已经知道了,自动装箱是通过Integer.valueOf()完成的,那我们就来看看这个方法的源码吧。

publicstaticIntegervalueOf(inti){if(i>=IntegerCache.low&&i<=IntegerCache.high)returnIntegerCache.cache[i+(-IntegerCache.low)];returnnewInteger(i);}

难不成是IntegerCache在作怪?你猜对了!

privatestaticclassIntegerCache{staticfinalintlow=-128;staticfinalinthigh;staticfinalIntegercache[];static{//highvaluemaybeconfiguredbypropertyinth=127;inti=parseInt(integerCacheHighPropValue);i=Math.max(i,127);h=Math.min(i,Integer.MAX_VALUE-(-low)-1);high=h;cache=newInteger[(high-low)+1];intj=low;for(intk=0;k<cache.length;k++)cache[k]=newInteger(j++);//range[-128,127]mustbeinterned(JLS75.1.7)assertIntegerCache.high>=127;}}

大致瞟一下这段代码你就全明白了。-128到127之间的数会从IntegerCache中取,然后比较,所以第二段代码(100在这个范围之内)的结果是true,而第三段代码(200不在这个范围之内,所以new出来了两个Integer对象)的结果是false。

看完上面的分析之后,我希望大家记住一点:当需要进行自动装箱时,如果数字在-128至127之间时,会直接使用缓存中的对象,而不是重新创建一个对象。

自动装拆箱是一个很好的功能,大大节省了我们开发人员的精力,但也会引发一些麻烦,比如下面这段代码,性能就很差。

longt1=System.currentTimeMillis();Longsum=0L;for(inti=0;i<Integer.MAX_VALUE;i++){sum+=i;}longt2=System.currentTimeMillis();System.out.println(t2-t1);

sum由于被声明成了包装类型Long而不是基本类型long,所以sum+=i进行了大量的拆装箱操作(sum先拆箱和i相加,然后再装箱赋值给sum),导致这段代码运行完花费的时间足足有2986毫秒;如果把sum换成基本类型long,时间就仅有554毫秒,完全不一个等量级啊。

05、最后

谢谢大家的阅读,原创不易,喜欢就点个赞,这将是我最强的写作动力。如果你觉得文章对你有所帮助,也蛮有趣的,就关注一下我的公众号,谢谢。

PS:偷偷地告诉你,后台回复「Java」还可领取价值399元的Java进阶资料,嘘。


普通会员

0

帖子

334

回复

338

积分
沙发
发表于 2023-08-17 23:35:49

好好好

您需要登录后才可以回帖 登录 | 立即注册

触屏版| 电脑版

技术支持 历史网 V2.0 © 2016-2017