zl程序教程

您现在的位置是:首页 >  Java

当前栏目

[Java基础]自动装箱与自动拆箱--为什么整型比较必须用equals?

2023-04-18 15:19:35 时间

偶然在项目里看到了下面这行代码,大家觉得这个if判断会存在什么问题吗?

if (129 == StatusEnum.OK.getCode()) {//其中OK是Integer code =129
    System.out.println("ok");
}

枚举定义如下:

@Getter
public enum StatusEnum {
    OK(129, "ok"),
    ERROR(0, "error"),
    ;
    private Integer code;
    private String desc;

    StatusEnum(Integer code, String desc) {
        this.code = code;
        this.desc = desc;
    }
}

在看这个问题前,我们先了解一下 自动装箱、自动拆箱

自动装箱、自动拆箱

1.定义

  • 自动装箱:自动将基本数据类型转换为封装类型
  • 自动拆箱:自动将封装类型转换为基本数据类型

关于数据类型,Java中定义了4大类基础的数据类型及其对应的封装类型

  • 整数:byte、int、short、long
  • 浮点数:float、double
  • 字符类型:char
  • 布尔类型:boolean

其对应关系如下:

基础类型 封装类型
byte Byte
int Integer
short Short
long Long
float Float
double Double
char Char
boolean Boolean

2.如何使用

使用起来非常简单,当=两端的类型分别是基础类型和其对应的封装类型时就会进行自动装箱或拆箱。

// 自动装箱
Integer a = 100;
// 自动拆箱
int b = a;

3.实现原理

  • 自动装箱:Java编译器为我们自动执行了 Integer.valueOf(int i),这是Integer提供的静态方法(Static Method);
/**
 * Returns an {@code Integer} instance representing the specified
 * {@code int} value.  If a new {@code Integer} instance is not
 * required, this method should generally be used in preference to
 * the constructor {@link #Integer(int)}, as this method is likely
 * to yield significantly better space and time performance by
 * caching frequently requested values.
 *
 * This method will always cache values in the range -128 to 127,
 * inclusive, and may cache other values outside of this range.
 *
 * @param  i an {@code int} value.
 * @return an {@code Integer} instance representing {@code I}.
 * @since  1.5
 */
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
  • 自动拆箱:Java编译器为我们自动执行了Integer.intValue(int i),这是Integer提供的实例方法方法(Instance Method);
/**
 * Returns the value of this {@code Integer} as an
 * {@code int}.
 */
public int intValue() {
    return value;
}

4.优点和缺点

通常引入新的解决方案的同时,也会引入新的问题,自动装箱、自动拆箱也不例外。

1.优点

基础数据类型只能满足最基础的数据使用,封装类型为我们提供了许多高级的使用场景。以String为例它为我们提供了很多字符串的查找、拼接、大小写转换等方法(见下图)。而自动装箱、自动拆箱在不影响我们日常使用的情况下让我们更灵活的在基础和封装类型中切换。
String提供的方法

2.缺点

1. NullPointException
由于自动拆箱时使用的Integer.intValue(int i)是实例方法,使用时需要Integer的实例存在才行,否则会产生NullPointException。例子如下:

Integer i = null;
boolean eq = 5 == i;//此处会抛出NullPointException

2. 缓存
自动装箱时使用了Integer.valueOf(int i),而在待装箱值iIntegerCache缓存范围[-128, 127]内时会优先使用缓存的Integer对象。具体如下:

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

我们看下下面这个例子:

int i = 127;
Integer one = I;
Integer two = I;
System.out.println(one == two);//进行地址比较
System.out.println(one.equals(two));//进行值比较

i=127时输出结果如下:

true
true

i=128时输出结果如下:

false
true

为什么不同的int装箱后的==比较结果不一样?
自动装箱的出来的对象,一般会默认为new构造出来的,两次自动装箱的对象应该是不同的对象,而整型的比较一般默认是进行值比较。

由于int在经过自动装箱时会使用[-128, 127]范围内的缓存对象,导致结果超出预期。

所以在使用自动装箱和自动拆箱时,大家要注意其背后的原理。

在了解了Integer Cache后,我们再看下文章开头提到的整型比较:

if (0 == StatusEnum.OK.getCode()) {//其中OK是Integer
    System.out.println("ok");
}

可以简化为下面的例子

System.out.println(127 == Integer.valueOf(127));
System.out.println(Integer.valueOf(127) == Integer.valueOf(127));

System.out.println(128 == Integer.valueOf(128));
System.out.println(Integer.valueOf(128) == Integer.valueOf(128));

System.out.println(129 == Integer.valueOf(129));//I比较Integer
System.out.println(Integer.valueOf(129) == Integer.valueOf(129));//Integer比较Integer

结果如下:

true
true
true
false

具体分析如下:

System.out.println(127 == Integer.valueOf(127));
// true,int 127的自动装箱会使用缓存,但是最终拆箱成int和int比较

System.out.println(Integer.valueOf(127) == Integer.valueOf(127));
// true,int 127的自动装箱会使用缓存对象,缓存对象相同

System.out.println(128 == Integer.valueOf(128));
// true,int 128的自动装箱会使用缓存,但是最终拆箱成int和int比较

System.out.println(Integer.valueOf(128) == Integer.valueOf(128));
// false,int 128的自动装箱不使用缓存,对象和对象比较不一样

大家可以看出以下结论:

  1. 当基本数据类型与封装类型进行==比较时,会进行拆箱比较;
  2. 封装类型与封装类型进行``==```比较时,等同于对象比较对象,会进行对象地址比较;
  3. Integer的自动装箱会为[-128, 127]范围内的值优先使用缓存对象

所以整型在进行比较时,大家不要使用==进行比较,参考下面Java开发手册(嵩山版)2020.08修订1.7.0截取内容:

  1. 【强制】所有整型包装类对象之间值的比较,全部使用 equals 方法比较。
    说明:对于 Integer var = ? 在-128 至 127 之间的赋值,Integer 对象是在 IntegerCache.cache 产生,
    会复用已有对象,这个区间内的 Integer 值可以直接使用==进行判断,但是这个区间之外的所有数据,都
    会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用 equals 方法进行判断。

5. 延伸问题:

为什么JDK设计时,Integer的自动装箱会为[-128, 127]范围内的值优先使用缓存对象?