Java Integer128陷阱详解

Integer128陷阱

什么是128陷阱

首先看一下问题是什么,测试代码如下:

public static void main(String[] args) {
    Integer i1 = 127;
    Integer i2 = 127;
    Integer i3 = 128;
    Integer i4 = 128;
    int i5 = 128;
    int i6 = 128;
    System.out.println(i1 == i2);
    System.out.println(i3 == i4);
    System.out.println(i5 == i6);
}

上述代码输出结果如下:

true
false
true

其中在判断(i3 == i4)时输出了false,很明显输出结果并不符合预期,在逻辑上也并不正确,而同样的128==128的比较在基本数据类型int下便可以正常输出true,首先要先弄清在128赋值时发生了什么,接下来通过javap -c反编译查看测试代码的字节码。

在开始分析前,首先要确定一个问题,使用equals比较时,无论赋什么值都可以正确输出true,下文所有讨论都建立在使用==比较的前提上,同时出现问题的另一个本质也可以说是因为==比较的是地址。

反编译分析

使用javap -c指令对.class文件反编译得到结果如下(部分截取):

public static void main(java.lang.String[]);
Code:
…省略
19: sipush 128
22: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
25: astore 4
27: sipush 128
30: istore 5
…省略
}

其中反编译结果的19、22、25行对应代码中Integer i4 = 128;,27、30行对应int i5 = 128;,可以很明显的看到,对比int,Integer在初始化时调用了Integer.valueOf()方法,这也就是Java的自动装箱机制,接下来简要介绍一下自动拆装箱。

Java自动拆装箱

自动装箱和自动拆箱的含义分别是:

  • 自动装箱:基本类型自动转换为包装类型。
  • 自动拆箱:包装类型自动转换为基本类型。

上文已经展现了自动装箱机制,即直接将一个int类型数据赋值给Integer对象时,会自动调用Integer.valueOf()方法,接下来通过javap -c反编译查看一下自动拆箱做了哪些工作,反编译目标代码段如下:

Integer i1 = 128;
int i2 = 128;
i2 = i1;

反编译结果如下:

public static void main(java.lang.String[]);
Code:
0: sipush 128
3: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
6: astore_1
7: sipush 128
10: istore_2
11: aload_1
12: invokevirtual #3 // Method java/lang/Integer.intValue:()I
15: istore_2
16: return
}

可以在反编译结果的12行中看到实际执行过程中调用了Integer.intValue()方法,该方法直接从Integer对象中返回int类型的value值。

实际上,下列伪代码中的写法是等效的:

//自动装箱 下列两种写法等效
Integer i1 = 128;//1
Integer i1 = Integer.valueOf(128);//2
//自动拆箱 下列两种写法等效
Integer i1 = 128;
int i2 = i1;//1
int i2 = i1.intValue();//2

可见自动拆装箱是Java语法糖的一种。

源码分析

既然自动装箱为我们自动调用了Integer.intValue()方法,那么就来查看一下该方法中做了哪些事,源码如下:

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

通过valueOf不难看出,想要理解该方法做了什么,脱不开IntegerCache,IntegerCache是Integer的一个静态内部类,源码如下:

	private static class IntegerCache {
		//下限-128
        static final int low = -128;
        static final int high;
        // 缓存数组 用于缓存-128~127的256个Integer对象
        static final Integer cache[];

        static {
            //上限127
            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);
                    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;
            //为cache创建对象
            for(int k = 0; k < cache.length; k++)
                cache[k] = new Integer(j++);

            assert IntegerCache.high >= 127;
        }

        private IntegerCache() {}
    }

结合上面两段源码,可见,设计时为了减少Integer对象创建的开销,提升整体效率,预先创建好了共256个Integer对象供后续使用。

整个IntegerCache类中的代码均被static修饰,目的是确保在被实际使用时就已经完成了初始化,IntegerCache类中使用cache[]数组缓存了从-128~127供256个Integer对象。

回到valueOf方法,可见,如果传入的int值在cache缓存-128~127的范围中,则直接返回预先创建好的对象,如果不在范围中,则创建一个新的Integer对象返回。

总结

至此便可以理解使用==比较两个value均为int型128的Integer对象为什么会被判断为false,可以总结为在Integer对象创建过程中:

  • 如果传入value数值在-128~127范围内,那么所有在这个范围内被创建的对象(句柄)实际都指向同一个地址,即被预创建Integer对象所在的地址。
  • 如果传入value数值不在范围内,那么每次被创建的对象(句柄)都指向一个新的且不同的地址,即通过new关键字由JVM分配的新地址。

地址指向示意图如下(不规范):
其中被static修饰的Integer数组cache在Java1.8以前存放在方法区中,Java1.8移除了方法区,静态变量改为存放在堆中,下图为Java1.8之前版本的示意图
在这里插入图片描述

  • 7
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

7rulyL1ar

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值