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 {//下限-128static final int low = -128;static final int high;// 缓存数组 用于缓存-128~127的256个Integer对象static final Integer cache[];static {//上限127int 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之前版本的示意图

Java Integer128陷阱详解相关推荐

  1. Java内存溢出详解之Tomcat配置

    Java内存溢出详解 转自:http://elf8848.iteye.com/blog/378805 一.常见的Java内存溢出有以下三种: 1. java.lang.OutOfMemoryError ...

  2. java基础(十三)-----详解内部类——Java高级开发必须懂的

    java基础(十三)-----详解内部类--Java高级开发必须懂的 目录 为什么要使用内部类 内部类基础 静态内部类 成员内部类 成员内部类的对象创建 继承成员内部类 局部内部类 推荐博客 匿名内部 ...

  3. Java类加载机制详解【java面试题】

    Java类加载机制详解[java面试题] (1)问题分析: Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数 ...

  4. Java线程池详解学习:ThreadPoolExecutor

    Java线程池详解学习:ThreadPoolExecutor Java的源码下载参考这篇文章:Java源码下载和阅读(JDK1.8) - zhangpeterx的博客 在源码的目录java/util/ ...

  5. Java 线程池详解学习:FixedThreadPool,CachedThreadPool,ScheduledThreadPool...

    Java常用的线程池有FixedThreadPool和CachedThreadPool,我们可以通过查看他们的源码来进行学习. Java的源码下载参考这篇文章:Java源码下载和阅读(JDK1.8) ...

  6. 关于Java的Classpath详解

    关于Java的Classpath详解 Java 的新入门者对classpath往往比较困惑,为何在开发环境中能运行的东东出去就不好,或在外面运行的东东挺溜的进了开发环境就死菜. java的优点就是他是 ...

  7. java异常体系结构详解

    java异常体系结构详解 参考文章: (1)java异常体系结构详解 (2)https://www.cnblogs.com/hainange/p/6334042.html 备忘一下.

  8. java异常处理机制详解

    java异常处理机制详解 参考文章: (1)java异常处理机制详解 (2)https://www.cnblogs.com/vaejava/articles/6668809.html 备忘一下.

  9. Java内存溢出详解

    Java内存溢出详解 一.常见的Java内存溢出有以下三种: 1. java.lang.OutOfMemoryError: Java heap space ----JVM Heap(堆)溢出 JVM在 ...

最新文章

  1. selenium+python自动化84-chrome手机wap模式
  2. Source Insight主题推荐和显示属性设置方法
  3. P2151-[SDOI2009]HH去散步【矩阵乘法】
  4. 【LeetCode笔记】581. 最短无序连续子数组(Java、数组)
  5. 信息学奥赛C++语言: 绝对素数
  6. 关于vue.js 编程导航的使用:实现路由配置和跳转页面
  7. 大型网站架构, 缓存的几点
  8. spring事务传递机制原理
  9. 【转】java对音频文件的频谱分析
  10. 设计模式-Builder模式详解
  11. TMS320DM8168浮点DSP C674x + ARM Cortex-A8开发板VGA输出接口
  12. lame编译 android,Android编译Lame的全平台so库方案2,并实现转码mp3
  13. 哥德尔:伟大的数学家与饱受精神疾病折磨的患者
  14. 近期基金有所上涨,你的基金回本了吗?如果回本了,你还会继续持仓吗?
  15. python + snownlp 正负面分析
  16. 失眠是怎么回事?睡眠障碍的诊断与治疗
  17. [体检]悲从中来,伤不起
  18. CCleaner软件清理系统注册表技巧
  19. 英语计算机手抄报图片大全,三年级简单英语手抄报漂亮
  20. js 中遇到英文双引号后端无法正常存储的解决方法

热门文章

  1. 关于病毒Backdoor.Gpigeon.uql
  2. .net日期控件使用
  3. 30天自制操作系统 综合设计
  4. 强大的TMUX分屏工具 详解快捷键 各种用法
  5. Flash 实验 遮罩层
  6. 区块链的数据结构(一)——区块、链
  7. think php框架案例,ThinkPHP框架设计及扩展详解
  8. Windows:win10中查看自己的电脑版本号的五种方法
  9. 漏洞复现_CVE-2017-0144 “永恒之蓝”漏洞
  10. 相亲交友源码的架构设计,合成复用原则的实现