包装类的自动装箱拆箱,==运算符及equals方法
文章目录
- 包装类的自动装箱拆箱,==运算符及equals方法
- 一个例子
- 程序反编译的结果及分析
- 自动装箱
- 看看Integer.valueOf(int i)源码
- 自动拆箱
- equals方法
- 总结:在以后写程序中需要注意的点
一个例子
我们先来看这样一个简单的程序:
public class SugarTest { public static void main(String[] args) { Integer a = 1; Integer b = 2; Integer c = 3; Integer d = 3; Integer e = 321; Integer f = 321; Long g = 3L; System.out.println(c == d); System.out.println(e == f); System.out.println(c == (a + b)); System.out.println(c.equals(a + b)); System.out.println(g == (a + b)); System.out.println(g.equals(a + b)); } }
上述程序的输出结果是什么?运行一下上述程序,看看真实输出与你预想的有什么不同。
上述程序的输出为:
对于上述结果我们不禁产生一个疑问,对于包装类,进行==
运算符比较和进行equals方法比较有哪些区别,如果==
运算符在遇到算术运算的情况下又会如何?
程序反编译的结果及分析
由于在使用包装类时,Java提供了自动装箱与拆箱的语法糖,对于上述程序,我们可能一时想不明白为什么会输出相应的结果。Javac编译器会进行解语法糖的过程,在使用javac命令将上述.java文件编译成.class文件后,自动装箱、拆箱将转化为对应的包装和还原方法,我们使用XJad反编译工具查看解语法糖后上述程序变成什么样了:
public class SugarTest { public SugarTest() { } public static void main(String args[]) { Integer integer = Integer.valueOf(1); Integer integer1 = Integer.valueOf(2); Integer integer2 = Integer.valueOf(3); Integer integer3 = Integer.valueOf(3); Integer integer4 = Integer.valueOf(321); Integer integer5 = Integer.valueOf(321); Long long1 = Long.valueOf(3L); System.out.println(integer2 == integer3); System.out.println(integer4 == integer5); System.out.println(integer2.intValue() == integer.intValue() + integer1.intValue()); System.out.println(integer2.equals(Integer.valueOf(integer.intValue() + integer1.intValue()))); System.out.println(long1.longValue() == (long)(integer.intValue() + integer1.intValue())); System.out.println(long1.equals(Integer.valueOf(integer.intValue() + integer1.intValue()))); } }
自动装箱
比较源程序与解语法糖后的程序,首先我们发现了之所以我们能在程序中使用Integer a = 1;
这样的语句,是因为自动装箱的存在。原本Integer a
是一个对象引用,1
是一个数值,我们是不能将数值赋给一个对象的,但借助于自动装箱,上述语句等价于Integer a = Integer.valueOf(1)
,Integer.valueOf(1)
调用valueOf
方法返回了一个Integer对象,而a成为了该对象的引用。
看看Integer.valueOf(int i)源码
接着我们来看第一条和第二条判断语句。第一条判断语句为true,而第二条判断语句为false,同样是整型包装类的==
运算符判断,为什么得到了相反的结果?我们知道,对于基本数据类型,==
判断的是值是否相等,对于引用类型,==
判断的是所引用对象是否为同一个对象。第一条判断为true,说明两个Integer.valueOf(3)
返回的对象是同一个对象,第二条判断为false,说明两个Integer.valueOf(321)
返回的对象是两个不同的对象。都是使用valueOf方法,为啥返回的对象有时相同有时不同?我们注意到调用上述两个valueOf方法,提供的参数值不同,难道是参数值在作怪吗,我们进入Integer.valueOf()源码内来看一看:
没看到源码,我们可能直观地认为Integer.valueOf()方法就是创建一个新的Integer对象并返回,但事实上并非如此。Integer.valueOf(int i)方法会先对i的范围进行判断,若其在-128~127
的范围内,则直接按照数组下标索引从预先创建好的Integer[]数组中取出对应的Integer对象返回,否则才会创建一个新的Integer对象。为什么要采用这样的设计,其实已经在注释中说明了,-128~127
范围内的值对应的Integer对象在程序中会被经常使用,设置缓存数组能加快valueOf()方法执行的速度,避免创建过多对象,提高空间利用效率。
看到了源码,之前的疑问也引刃而解了,对于Integer c = Integer.valueOf(3);Integer d = Integer.valueOf(3);
,由于3在-128~127范围内,实际上c和d引用的是缓存数组中的同一个对象,执行上述代码并没有进行新对象的创建。c == d
的判断结果为true。对于Integer e = Integer.valueOf(321);Integer f = Integer.valueOf(321);
,由于321超过了-128~127的范围,因此调用Integer.valueOf(321)
方法会创建一个新的对象返回,e和f引用的是两个不同的对象,e == f
的判断结果为false。
自动拆箱
来看看==
运算符在遇到算术运算的情况下变成什么样了?对于System.out.println(c == (a + b));
,解语法糖后变成了System.out.println(c.intValue() == a.intValue() +b.intValue());
我们之所以可以对两个包装类实例进行加减等运算操作,是因为有自动拆箱的存在。对象和对象之间是不能加减的,但基于自动拆箱,我们将其变为了包装类实例中value值之间的加减操作。
需注意==
运算符在没有遇到算术运算时,是不会自动拆箱的,我们可以看反汇编程序中第一和第二条判断语句。因为在没遇到算术运算时,Javac编译器会认为==
是在进行引用类型之间的判断,不需要自动拆箱,而遇到算术运算时,会认为==
是在进行数值类型之间的判断,这时候就需要自动拆箱了。而自动拆箱后,对于不同数据类型之间的判断,Javac编译器会进行数据类型间的自动转换,即由低字节向高字节进行转换。于是我们看到了System.out.println(g.longValue() == (long)(a.intValue() + b.intValue()));
equals方法
我们知道,每个类都有从Object类中继承的equals()方法,可以重写equals()方法,若不进行重写,所继承的equals()方法其实就是在进行==
运算符判断:
那么,包装类有没有重写equals方法呢?答案是肯定的,我们判断两个包装类是否相等,肯定是想判断两者的值是否相等,而不是判断两者是否引用同一个对象。看看Integer.equals()方法源码来验证我们的想法:
对于Integer包装类的equals(Object obj)方法,我们会先判断obj是否是Integer类的实例,若不是,则直接return false,否则进行包装类实例对应的值是否相等的判断。
System.out.println(c.equals(a + b));
在解语法糖后变成了这样:System.out.println(integer2.equals(Integer.valueOf(integer.intValue() + integer1.intValue())));
,由于遇到了算术运算,先进行了自动拆箱,又因为只有两个包装类对象之间才能用包装类的equals方法进行判断,所以又进行了自动装箱,最后调用equals方法进行值是否相等的判断。而System.out.println(c.equals(a + b));
输出true,System.out.println(g.equals(a + b));
输出false的原因想必不用多说了,前者两个Integer包装类实例的值相等,后者由于是两个不同包装类实例间的equals比较,直接返回false。
总结:在以后写程序中需要注意的点
经过上述分析,造成文章开头例子中的输出结果的理由我们已经全部弄清楚了,那么我们在写程序时需要注意什么呢?这里我直接引用阿里Java开发手册中的一条强制性编码规范:
对于Integer类型,我们就知道了由于缓存数组导致的大坑,无法使用==
进行值的比较。因此当我们进行相同类型包装类对象之间值的比较时,就别用==
判断了,都使用equals进行判断。
在IDEA中装上阿里代码规范插件后,也会提醒大家这一点: