[jjzhu学java]之自动装箱的陷阱
来源:互联网 发布:斗鱼主播直播软件 编辑:程序博客网 时间:2024/05/18 10:56
自动装箱、拆箱的陷阱
装箱与拆箱
java语言中为每种基本数据类型(int,float,double…)都提供了与之对应的包装器类型(Integer,Float,Double)。从java se5之后就开始提供了自动装箱的特性。想要得到一个数值为2016的Integer时,只需要如下的赋值语句:
//Integer a = Integer.valueOf(2016);Integer a = 2016;
该语句就会自定根据=右边的数值创建相应的Integer,这个过程就是自动装箱。
拆箱与装箱是相对应的,即自动将包装器类型转换为基本类型,如下赋值语句将会触发拆箱操作
int n = a; //n=2016
java提供了这样的语法糖,它到底是怎么实现的呢,我们可以写如下代码进行编译与反编译测试
public class GenericTypes { public static void main(String[] args) { Integer a = 2016; int n = a; System.out.println(n); }}
在该java文件的同级目录下运行如下命令进行编译
javac GenericTypes.java
然后用jd-gui对生成的class文件进行反编译,进过编译->反编译后的代码会和我们之前写的一样吗?事实显示并不一样。
public class GenericTypes{ public static void main(String[] paramArrayOfString) { Integer localInteger = Integer.valueOf(2016); int i = localInteger.intValue(); System.out.println(i); }}
从结果中可以很清楚的看到,赋值语句与之前截然不同,其对a的赋值其实是自动调用了Integer.valueOf()方法,而对n=a这赋值语句自动调用了Integer.intValue(),对于其他包装类型也是如此,自动装箱与拆箱会自动的调用包装类中的valueOf与xxxValue方法。
装箱陷阱
Integer自动装箱陷阱
之前已经演示了包装类型自动装箱与拆箱的原理。这里先看下面的代码,思考一下输出将会是多少,再进行下一步的讨论。代码片段摘自《深入理解java虚拟机:JVM的高级特性与最佳实践》的P274。
public class GenericTypes { public static void main(String[] args) { Integer a = 1; Integer b = 2; Integer c = 3; Integer d = 3; Integer e = 321; Integer f = 321; Long g = 3L; System.out.println(c == d); System.out.println(e == f); System.out.println(c == (a+b)); System.out.println(c.equals(a+b)); System.out.println(g == (a+b)); System.out.println(g.equals(a+b)); }}
输出的结果是
truefalsetruetruetruefalse
why?c == d为true,e == f为什么为false…..
这里我们进行进一步的讨论。之前我们已经讨论过,Integer自动装箱会自动调用Integer.valueOf()方法,我们可以查看其具体实现是怎么样的,其源码如下:
public static Integer valueOf(int i) { if(i >= -128 && i <= IntegerCache.high) return IntegerCache.cache[i + 128]; else return new Integer(i); }
这里你就会发现,这里有个判断,如果输入的值i在[-128, IntegerCache.high]范围内时,返回的是IntegerCache.cache[i + 128];这个IntegerCache是什么,显而易见,它是“整型缓存”,也就是说Integer类型对[-128, IntegerCache.high]范围内的数字进行了缓存,进一步看IntegerCache类的源代码:
private static class IntegerCache { static final int high; static final Integer cache[]; static { final int low = -128; // high value may be configured by property int h = 127; if (integerCacheHighPropValue != null) { // Use Long.decode here to avoid invoking methods that // require Integer's autoboxing cache to be initialized int i = Long.decode(integerCacheHighPropValue).intValue(); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - -low); } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); } private IntegerCache() {} }
从代码中可以知道,IntegerCache默认对[-128,127] 用了一个cache数据进行了缓存的,缓存最大值h(默认127)可以配置,也就是说-128-127之间的Integer都是对cache中的Integer包装类型的一个引用,这里就可以解释为什么System.out.println(c == d); 为true,而 System.out.println(e == f); 为false,因为321显然>127。
因为缓存最大值是可以配置的,Integer类中源码如是写道:
/** * Cache to support the object identity semantics of autoboxing for values between * -128 and 127 (inclusive) as required by JLS. * * The cache is initialized on first usage. During VM initialization the * getAndRemoveCacheProperties method may be used to get and remove any system * properites that configure the cache size. At this time, the size of the * cache may be controlled by the vm option -XX:AutoBoxCacheMax=<size>. */ // value of java.lang.Integer.IntegerCache.high property (obtained during VM init) private static String integerCacheHighPropValue; static void getAndRemoveCacheProperties() { if (!sun.misc.VM.isBooted()) { Properties props = System.getProperties(); integerCacheHighPropValue = (String)props.remove("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) System.setProperties(props); // remove from system props } }
我们可以通过-XX:AutoBoxCacheMax=size来进行对缓存最大值进行配置。这里我们配置为400,重新运行,System.out.println(e == f); 将输出true。
在看看最后一个System.out.println(g.equals(a+b));为什么会输出false,查看源码就立即知道答案:
public boolean equals(Object obj) { if (obj instanceof Long) { return value == ((Long)obj).longValue(); } return false; }
包装类的equals只比较同类型的对象。我们对测试代码进行反编译
public class GenericTypes{ public static void main(String[] paramArrayOfString) { Integer localInteger1 = Integer.valueOf(1); Integer localInteger2 = Integer.valueOf(2); Integer localInteger3 = Integer.valueOf(3); Integer localInteger4 = Integer.valueOf(3); Integer localInteger5 = Integer.valueOf(321); Integer localInteger6 = Integer.valueOf(321); Long localLong = Long.valueOf(3L); System.out.println(localInteger3 == localInteger4); System.out.println(localInteger5 == localInteger6); System.out.println(localInteger3.intValue() == localInteger1.intValue() + localInteger2.intValue()); System.out.println(localInteger3.equals(Integer.valueOf(localInteger1.intValue() + localInteger2.intValue()))); System.out.println(localLong.longValue() == localInteger1.intValue() + localInteger2.intValue()); System.out.println(localLong.equals(Integer.valueOf(localInteger1.intValue() + localInteger2.intValue()))); }}
可以看到,传给equals是基础int类型,自然返回的是false。
其他包装类型
其他的包装类型都可以根据上诉方法进行分析,这里只给出几个测试代码,可以先思考,然后进行实际检测
public static void main(String[] args) { Boolean b1 = true; Boolean b2 = true; System.out.println(b1 == b2); System.out.println(b1.equals(b2)); Character c1 = 'a'; Character c2 = 'a'; System.out.println(c1 == c2); Float f1 = 0.1f; Float f2 = 0.1f; System.out.println(f1 == f2); Long l1 = 10L; Long l2 = 10L; Long l3 = 210L; Long l4 = 210L; System.out.println(l1 == l2); System.out.println(l3 == l4); Double a = 10.0; Double b = 10.0; System.out.println(a == b); System.out.println(a.equals(b)); }
总结
java语法糖提供的自动装箱和拆箱过程,可以通过编译->反编译来看其具体是如何进行装箱和拆箱的,然后对其操作进行源码查看,进一步了解其装、拆过程。
- [jjzhu学java]之自动装箱的陷阱
- Java的自动装箱陷阱
- Java自动装箱的陷阱
- JAVA语法糖之自动装箱的陷阱
- java自动装箱陷阱
- Java自动装箱和自动拆箱的陷阱
- [jjzhu学java]之solr4.9同步mysql数据
- [jjzhu学java]之JDK集合框架源码分析
- 文章自动装箱的陷阱
- JVM学习笔记--语法糖之 自动装箱的陷阱
- java中自动装箱拆箱的陷阱
- Integer的自动拆箱装箱陷阱
- 自动装箱的陷阱--读书笔记①
- [jjzhu学java]之深入理解JVM之垃圾收集器与内存分配策略
- Java的自动装箱
- [jjzhu学java之多线程笔记]java并发机制的底层实现原理
- java自动装箱之Integer
- java 自动装箱与拆箱 中的陷阱
- Tomcat启动报错,ClassNotFoundException
- python学习笔记 切片
- Android初步编程
- C语言中使用内存较大的二维数组出错的解决办法
- 打印100以内的素数(质数)----C++实现、python实现
- [jjzhu学java]之自动装箱的陷阱
- SAP MB52改为ALV显示格式
- urllib2抓取网页
- 电源模块
- Java使用URL获取网页内容
- 文学创作的艺术手法
- android 实时显示系统时间
- 如何区分风热感冒和风寒感冒
- 通过网络连接获取html字符串