包装类的自动装箱拆箱,==运算符及equals方法

简介: 包装类的自动装箱拆箱,==运算符及equals方法详解

包装类的自动装箱拆箱,==运算符及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));
  }
}

  上述程序的输出结果是什么?运行一下上述程序,看看真实输出与你预想的有什么不同。

  上述程序的输出为:

1.png

  对于上述结果我们不禁产生一个疑问,对于包装类,进行==运算符比较和进行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()源码内来看一看:

2.png

3.png

  没看到源码,我们可能直观地认为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()方法其实就是在进行==运算符判断:

4.png

  那么,包装类有没有重写equals方法呢?答案是肯定的,我们判断两个包装类是否相等,肯定是想判断两者的值是否相等,而不是判断两者是否引用同一个对象。看看Integer.equals()方法源码来验证我们的想法:

5.png

  对于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开发手册中的一条强制性编码规范:

6.png

  对于Integer类型,我们就知道了由于缓存数组导致的大坑,无法使用==进行值的比较。因此当我们进行相同类型包装类对象之间值的比较时,就别用==判断了,都使用equals进行判断。

  在IDEA中装上阿里代码规范插件后,也会提醒大家这一点:

7.png

目录
相关文章
|
7月前
|
Java
包装类的使用
包装类的使用
31 0
|
8月前
|
缓存
包装类
包装类
37 0
|
8月前
|
存储 Java
包装类和基本数据类型
包装类和基本数据类型
43 0
|
8月前
|
缓存 Java 编译器
自动拆箱与装箱
自动拆箱与装箱
|
5月前
|
缓存 Java
包装类(装箱&拆箱&数据类型转换)
​ 在Java5 中添加了两个新特性,那就是自动装箱和拆箱,因为基本类型的广泛使用,但是Java 又是面向对象的语言,所以提供了包装类型的支持。
23 0
|
8月前
|
缓存 Java
基本数据类型包装类
基本数据类型包装类
44 0
|
存储 缓存 Java
Java基本类型和包装类什么情况下判断相等(“==“或“equals“)?
Java基本类型和包装类什么情况下判断相等(“==“或“equals“)?
209 0
Java基本类型和包装类什么情况下判断相等(“==“或“equals“)?
|
Java 大数据
6、包装类及其相关
包装类及其相关
106 0
6、包装类及其相关
Integer自动拆箱与自动装箱的相关问题
Integer自动拆箱与自动装箱的相关问题