前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【优雅的避坑】不要轻易使用==比较两个Integer的值

【优雅的避坑】不要轻易使用==比较两个Integer的值

作者头像
行百里er
发布2020-12-02 15:10:41
8690
发布2020-12-02 15:10:41
举报
文章被收录于专栏:JavaJourney

引入

没有前戏,直奔代码,来看下面这段代码的运行结果:

代码语言:javascript
复制
@Test
public void test() {
    Integer i = 666;
    int j = i + 1;
    System.out.println("j = " + j);
}

自然,我们都知道会打印 j = 667,曾经我很好奇,i是Integer对象,属于包装类型,而j是int基础数据类型,他俩怎么会在一起运算呢?直到我扒开Integer的外表,直接看到了他的内涵...

当然我是借助于工具看到程序运行的内涵的,IDEA的jclasslib Bytecode viewer这个插件,能够反编译代码,而且,反编译出来的指令,可以直接链接到官网上查看。 安装好插件后,如图所示,就可以看反编译后的代码指令了:

使用Jclasslib插件查看程序字节码

上面这段程序的字节码指令:

反编译

那么这些指令都是什么玩意呢?

自动装箱与自动拆箱

自动装箱(auto boxing)和自动拆箱(auto unboxing)是Java 5引入的功能,有了这两个功能,Java在编译阶段,会根据上下文对数据类型自动进行转换,可以保证不同的写法在运行时等价。

自动装箱:将值类型转换成引用类型的过程 自动拆箱:将引用类型转换成值类型的过程

代码语言:javascript
复制
Integer i = 666;
int j = i + 1;

这两行代码就是就体现了自动装箱与自动拆箱。

来看一下代码编译后的字节码指令:

代码语言:javascript
复制
 0 sipush 666
 3 invokestatic #2 <java/lang/Integer.valueOf>
 6 astore_1
 7 aload_1
 8 invokevirtual #3 <java/lang/Integer.intValue>
11 iconst_1
12 iadd
13 istore_2
14 return

第3行:invokestatic #2 <java/lang/Integer.valueOf>

invokestatic

意思是调用类的静态方法,后面指出了是调用Integer的valueOf这个静态方法,也就是说在编译阶段Java就自动把装箱转换成了Integer.valueOf

第8行:invokevirtual #3 <java/lang/Integer.intValue>

invokevirtual

调用类实例的方法,这个拆箱就是说在编译阶段就调用了Integer的intValue方法。

分别来看一下valueOfintValue这两个方法源码。

valueOf:

代码语言:javascript
复制
/**
* 返回表示指定int值的整数实例。如果不需要新的Integer实例,
* 那么通常应该优先使用该方法,而不是构造函数Integer(int),
* 因为通过缓存经常请求的值,该方法可能会产生更好的空间和时间性能。
* 此方法将始终缓存范围为(-128,127]的值,并可能缓存此范围之外的其他值。
*/
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

水越来越深了,这时又跑出来个IntegerCache

IntegerCache:

代码语言:javascript
复制
/**
 * 缓存支持自动装箱为-128,并根据需要通过JLS 127(含)之间的值的对象标识语义。
 * 缓存是在第一次使用初始化的。
 * 缓存的大小可以通过-XX:AutoBoxCacheMax=<size>选项设置。
 * 在虚拟机初始化的过程中,在系统类sun.misc.VM中设置并保存java.lang.Integer.IntegerCache.high
 **/
private static class IntegerCache {
    static final int low = -128;
    static final int high;
    static final Integer cache[];

    static {
        // high value may be configured by property
        int h = 127;
        String integerCacheHighPropValue =
            sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
        if (integerCacheHighPropValue != null) {
            try {
                int i = parseInt(integerCacheHighPropValue);
                i = Math.max(i, 127);
                // Maximum array size is Integer.MAX_VALUE
                h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
            } catch( NumberFormatException nfe) {
                // If the property cannot be parsed into an int, ignore it.
            }
        }
        high = h;

        cache = new Integer[(high - low) + 1];
        int j = low;
        for(int k = 0; k < cache.length; k++)
            cache[k] = new Integer(j++);

        // range [-128, 127] must be interned (JLS7 5.1.7)
        assert IntegerCache.high >= 127;
    }

    private IntegerCache() {}
}

这就是所谓的Integer缓存,在虚拟机初始化的过程中,就已经缓存好了(-128,127]之间的数据(自动装箱)。

再看一下intValue()方法:

代码语言:javascript
复制
//...
// The value of the {@code Integer}.
int private final int value;
//...

/**
* 以整型数的形式返回该Integer的值
*/
public int intValue() {
    return value;
}

以整型数的形式返回该Integer的值,对应拆箱。

比较两个Integer的值

看代码:

代码语言:javascript
复制
@Test
public void test() {
    Integer i1 = 66;
    Integer i2 = 66;
    System.out.println("66 == 66 ? " + (i1 == i2));
    Integer i3 = 666;
    Integer i4 = 666;
    System.out.println("666 == 666 ? " + (i3 == i4));
}

到这大家肯定都知道一个是true,一个是false:

但是,这是为什么呢?被问到这个丝毫不慌,其实前文已经解释了,Integer里面搞了个IntegerCache这个东西,它默认缓存了(-128,127]之间的数据(可以通过 -XX:AutoBoxCacheMax= 设置),并用数组Integer cache[]保存起来了,也就是说在(-128,127]之间的数值都是IntegerCache.cache[] 数组中的同一个Integer对象。

66在(-128,127]之间,666大于127了,所以i1 == i2为true,而i3 == i4为false。

避坑

那么怎么正确的比较两个Integer的值呢?用equals()!

equals:

代码语言:javascript
复制
/**
* 将此对象与指定对象进行比较。
* 当且仅当参数不为null且为包含与此对象相同整型值的整数对象时,结果为真。
*/
public boolean equals(Object obj) {
    if (obj instanceof Integer) {
        return value == ((Integer)obj).intValue();
    }
    return false;
}

哈哈,equals方法比较的是两个对象的整型值,不用考虑是基础类型还是引用类型了,一律转换成int类型再进行比较!

这也就是阿里Java开发手册上说的强制使用equals方法比较整型包装类对象的值

END

推荐阅读

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-09-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 行百里er 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 引入
  • 自动装箱与自动拆箱
  • 比较两个Integer的值
  • 避坑
  • 推荐阅读
相关产品与服务
验证码
腾讯云新一代行为验证码(Captcha),基于十道安全栅栏, 为网页、App、小程序开发者打造立体、全面的人机验证。最大程度保护注册登录、活动秒杀、点赞发帖、数据保护等各大场景下业务安全的同时,提供更精细化的用户体验。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档