(转)Java中的装箱与拆箱
在J2SE5.0后推出了自动装箱和拆箱的功能,以提高我们的开发效率,然而自动装箱和拆箱实际上是通过编译器来支持的(并非语言本身,或者说虚拟机),因而这种支持也隐藏了部分内部实质,再加上某些类的优化(比如Integer里面的缓存等,参看关于缓存节),就更加容易在特定的环境下产生问题,并且如果不知道原来还无法调试。以下先是简单的介绍了编译器对装箱和拆箱的实现,并根据实现简单介绍一下可能会遇到的几个问题。
装箱和拆箱实现
以下装箱和拆箱代码:
int intValue = (Integer)value;
Integer newIntValue = new Integer(10);
编译成字节码如下:
0 bipush 10
2 invokestatic java.lang.Integer.valueOf(int) : java.lang.Integer [20]
5 astore_1 [value]
6 aload_1 [value]
7 checkcast java.lang.Integer [21]
10 invokevirtual java.lang.Integer.intValue() : int [26]
13 istore_2 [intValue]
14 new java.lang.Integer [21]
17 dup
18 bipush 10
20 invokespecial java.lang.Integer(int) [30]
23 astore_3 [newIntValue]
从以上字节码可以看到10首先调用valueOf方法转换为Integer实例,再赋值该value,而value强制转换成Integer类后,会调用intValue方法,后赋值给intValue。这就是用编译器来实现装箱和拆箱。
奇怪的NullPointerException
查看以下代码:
int intValue = value;
可以编译通过,但是运行的时候却会发生NullPointerException。这是由什么引起的呢?依然看一下字节码就可以了:
0 aconst_null
1 astore_1 [value]
2 aload_1 [value]
3 invokevirtual java.lang.Integer.intValue() : int [20]
6 istore_2 [intValue]
从字节码中可以看到,从value赋值该intValue事实上是直接在value实例上调用intValue函数。
对当前代码,我们可以一眼就看出当前value是null的问题,但是如果这个null是在很远以外的地方赋值的呢?或者是间接赋值呢?这个时候遇到这种问题就会比较诡异了。
相等与不相等问题
查看一下代码:
Integer value2 = 100;
System.out.println("value1 == value2 is " + (value1 == value2));
Integer value3 = 200;
Integer value4 = 200;
System.out.println("value3 == value4 is " + (value3 == value4));
这段代码会是什么结果?
value1 == value2 is true
value3 == value4 is false
两段代码就是值不一样,其他的都一样,竟然会有区别?这个奥妙就因为装箱过程中调用的是valueOf方法,而valueOf方法对值在-128到127之间的数值缓存了(参见关于缓存一节),因而value1和value2的引用是相同的,而value3和value4的引用是不一样的,而==比较的是引用,因而才会出现以上的结果。
这确的做法应该是:
Integer value2 = 100;
System.out.println("value1 == value2 is " + (value1.equals(value2)));
Integer value3 = 200;
Integer value4 = 200;
System.out.println("value3 == value4 is " + (value3.equals(value4)));
这样的结果就是预料的结果了:
value1 == value2 is true
value3 == value4 is true
所以我们要慎用“==”操作符。
String中的相等与不等
在String中也有类似的情况,查看一下代码:
String str2 = "abc";
System.out.println("str1 == str2 is " + (str1 == str2));
String str3 = new String("abc");
String str4 = new String("abc");
System.out.println("str3 == str4 is " + (str3 == str4));
执行结果:
str1 == str2 is true
str3 == str4 is false
这是因为str1和str2使用的是同一个字符串,即在字符常量中的字符串,而str3和str4在使用字符常量中的字符为参数又创建出了两个新的字符串对象,因而在引用比较情况下是不等的。我们可以从字节码中得到这些信息(删除打印的代码):
0 ldc <String "abc"> [20]
2 astore_1 [str1]
3 ldc <String "abc"> [20]
5 astore_2 [str2]
6 new java.lang.String [22]
9 dup
10 ldc <String "abc"> [20]
12 invokespecial java.lang.String(java.lang.String) [24]
15 astore_3 [str3]
16 new java.lang.String [22]
19 dup
20 ldc <String "abc"> [20]
22 invokespecial java.lang.String(java.lang.String) [24]
25 astore 4 [str4]
正确的做法还是调用equals方法,而不是使用“==”操作符。
关于缓存
据目前信息,有缓存的类有:Byte、Short、Integer、Long以及Boolean类。而这种缓存也只是在调用valueOf(静态)方法的时候才会存在(装箱正是调用了valueOf方法)。对整型,缓存的值都是-128到127(包括-128和127)之间,其他值都不缓存,而对Boolean类型只有true和false值。代码如下:
public static Integer valueOf(int i) {
if(i >= -128 && i <= IntegerCache.high)
return IntegerCache.cache[i + 128];
else
return new Integer(i);
}
public final class Boolean {
public static Boolean valueOf(boolean b) {
return (b ? TRUE : FALSE);
}
首先看一段代码(使用JDK 5),如下:
- public class Hello
- {
- public static void main(String[] args)
- {
- int a = 1000, b = 1000;
- System.out.println(a == b);
- Integer c = 1000, d = 1000;
- System.out.println(c == d);
- Integer e = 100, f = 100;
- System.out.println(e == f);
- }
- }
输出结果:
- true
- false
- true
The Java Language Specification, 3rd Edition 写道:
- 为了节省内存,对于下列包装对象的两个实例,当它们的基本值相同时,他们总是==:
- Boolean
- Byte
- Character, \u0000 - \u007f(7f是十进制的127)
- Integer, -128 — 127
查看jdk源码,如下:
- /**
- * 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
- }
- }
- 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() {}
- }
- /**
- * Returns a <tt>Integer</tt> instance representing the specified
- * <tt>int</tt> value.
- * If a new <tt>Integer</tt> instance is not required, this method
- * should generally be used in preference to the constructor
- * {@link #Integer(int)}, as this method is likely to yield
- * significantly better space and time performance by caching
- * frequently requested values.
- *
- * @param i an <code>int</code> value.
- * @return a <tt>Integer</tt> instance representing <tt>i</tt>.
- * @since 1.5
- */
- public static Integer valueOf(int i) {
- if(i >= -128 && i <= IntegerCache.high)
- return IntegerCache.cache[i + 128];
- else
- return new Integer(i);
- }
这儿的IntegerCache有一个静态的Integer数组,在类加载时就将-128 到 127 的Integer对象创建了,并保存在cache数组中,一旦程序调用valueOf 方法,如果i的值是在-128 到 127 之间就直接在cache缓存数组中去取Integer对象。
再看其它的包装器:
- Boolean:(全部缓存)
- Byte:(全部缓存)
- Character(<= 127缓存)
- Short(-128 — 127缓存)
- Long(-128 — 127缓存)
- Float(没有缓存)
- Doulbe(没有缓存)
同样对于垃圾回收器来说:
- Integer i = 100;
- i = null;//will not make any object available for GC at all.
这里的代码不会有对象符合垃圾回收器的条件,这儿的i虽然被赋予null,但它之前指向的是cache中的Integer对象,而cache没有被赋null,所以Integer(100)这个对象还是存在。
而如果i大于127或小于-128则它所指向的对象将符合垃圾回收的条件:
- Integer i = 10000;
- i = null;//will make the newly created Integer object available for GC.