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 {//下限-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陷阱详解相关推荐
- Java内存溢出详解之Tomcat配置
Java内存溢出详解 转自:http://elf8848.iteye.com/blog/378805 一.常见的Java内存溢出有以下三种: 1. java.lang.OutOfMemoryError ...
- java基础(十三)-----详解内部类——Java高级开发必须懂的
java基础(十三)-----详解内部类--Java高级开发必须懂的 目录 为什么要使用内部类 内部类基础 静态内部类 成员内部类 成员内部类的对象创建 继承成员内部类 局部内部类 推荐博客 匿名内部 ...
- Java类加载机制详解【java面试题】
Java类加载机制详解[java面试题] (1)问题分析: Class文件由类装载器装载后,在JVM中将形成一份描述Class结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数 ...
- Java线程池详解学习:ThreadPoolExecutor
Java线程池详解学习:ThreadPoolExecutor Java的源码下载参考这篇文章:Java源码下载和阅读(JDK1.8) - zhangpeterx的博客 在源码的目录java/util/ ...
- Java 线程池详解学习:FixedThreadPool,CachedThreadPool,ScheduledThreadPool...
Java常用的线程池有FixedThreadPool和CachedThreadPool,我们可以通过查看他们的源码来进行学习. Java的源码下载参考这篇文章:Java源码下载和阅读(JDK1.8) ...
- 关于Java的Classpath详解
关于Java的Classpath详解 Java 的新入门者对classpath往往比较困惑,为何在开发环境中能运行的东东出去就不好,或在外面运行的东东挺溜的进了开发环境就死菜. java的优点就是他是 ...
- java异常体系结构详解
java异常体系结构详解 参考文章: (1)java异常体系结构详解 (2)https://www.cnblogs.com/hainange/p/6334042.html 备忘一下.
- java异常处理机制详解
java异常处理机制详解 参考文章: (1)java异常处理机制详解 (2)https://www.cnblogs.com/vaejava/articles/6668809.html 备忘一下.
- Java内存溢出详解
Java内存溢出详解 一.常见的Java内存溢出有以下三种: 1. java.lang.OutOfMemoryError: Java heap space ----JVM Heap(堆)溢出 JVM在 ...
最新文章
- selenium+python自动化84-chrome手机wap模式
- Source Insight主题推荐和显示属性设置方法
- P2151-[SDOI2009]HH去散步【矩阵乘法】
- 【LeetCode笔记】581. 最短无序连续子数组(Java、数组)
- 信息学奥赛C++语言: 绝对素数
- 关于vue.js 编程导航的使用:实现路由配置和跳转页面
- 大型网站架构, 缓存的几点
- spring事务传递机制原理
- 【转】java对音频文件的频谱分析
- 设计模式-Builder模式详解
- TMS320DM8168浮点DSP C674x + ARM Cortex-A8开发板VGA输出接口
- lame编译 android,Android编译Lame的全平台so库方案2,并实现转码mp3
- 哥德尔:伟大的数学家与饱受精神疾病折磨的患者
- 近期基金有所上涨,你的基金回本了吗?如果回本了,你还会继续持仓吗?
- python + snownlp 正负面分析
- 失眠是怎么回事?睡眠障碍的诊断与治疗
- [体检]悲从中来,伤不起
- CCleaner软件清理系统注册表技巧
- 英语计算机手抄报图片大全,三年级简单英语手抄报漂亮
- js 中遇到英文双引号后端无法正常存储的解决方法