2008年6月1日星期日

Java的自动装箱与拆箱

在JDK 1.5之前,只能往集合类中存放对象。基本类型的数据只能先包装成包装类对象才能放进去。在JDK 1.5中引入了自动装箱和拆箱的功能。

Integer i = 100;

相当于编译器自动为您作以下的语法编译:

Integer i = new Integer(100);

所以自动装箱与拆箱的功能是所谓的“编译器蜜糖”(Compiler Sugar),虽然使用这个功能很方便,但在程序运行阶段您得了解Java的语义。例如下面的程序是可以通过编译的:

Integer i = null;
int j = i;

这样的语法在编译时期是合法的,但是在运行时期会有错误,因为这种写法相当于:

Integer i = null;
int j = i.intValue();

null表示i没有参考至任何的对象实体,它可以合法地指定给对象参考名称。由于实际上i并没有参考至任何的对象,所以也就不可能操作intValue()方法,这样上面的写法在运行时会出现NullPointerException错误。

自动装箱、拆箱的功能提供了方便性,但隐藏了一些细节,所以必须小心。

那么自动装箱的原理是什么呢?

《使用JProfiler剖析Java Application中对象的分配和回收(gc)》一文中,原先的代码是:

package my;

import java.io.IOException;
import java.util.ArrayList;

public class Test {

    public static void main(String[] args) throws IOException {
        char ch=' ';
        while(ch!='n')
            ch=(char)System.in.read();
        for(int i=0;i<1000;i++)
            test();
        ch=' ';
        while(ch!='n')
            ch=(char)System.in.read();
    }

    public static void test(){
        ArrayList<Integer> arr=new ArrayList<Integer>();
        for(int i=0;i<10000;i++)
            arr.add((int)(Math.random()*100));
        //arr.clear();
        //arr=null;
    }
}

后来发现,程序运行结束以后总有256个Integer对象不能回收,百思不得其解。后来反编译了代码,看到实际上是编译器自动完成了将基本类型数据变成包装类对象的操作。下面是反编译的结果,注意红色的部分是编译器做的工作。

package my;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;

public class Test
{
  public static void main(String[] args)
    throws IOException
  {
    char ch = ' ';
    while (ch != 'n')
      ch = (char)System.in.read();
    for (int i = 0; i < 1000; ++i)
      test();
    ch = ' ';
    while (ch != 'n')
      ch = (char)System.in.read();
  }

  public static void test()
  {
    ArrayList arr = new ArrayList();
    for (int i = 0; i < 10000; ++i)
      arr.add(Integer.valueOf((int)(Math.random() * 100.0D)));
  }
}

而这个valueOf()方法的源代码如下:

/**
     * 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) {
    final int offset = 128;
    if (i >= -128 && i <= 127) { // must cache
        return IntegerCache.cache[i + offset];
    }
        return new Integer(i);
    }

IntegerCache是Integer类的内部类

private static class IntegerCache {
    private IntegerCache(){}

    static final Integer cache[] = new Integer[-(-128) + 127 + 1];

    static {
        for(int i = 0; i < cache.length; i++)
        cache[i] = new Integer(i - 128);
    }
    }

当要包装的数值在-128-+127之间时,就会产生256个Integer对象,并返回其中的一个。如果在此范围之外,则直接返回一个Integer对象。这样就不难理解为什么最后有256个不能回收的Integer对象了。

带来的问题:

下面的三个程序输出各是什么?

public class AutoBoxDemo {
    public static void main(String[] args) {
        Integer i1 = 100;
        Integer i2 = 100;
        if (i1 == i2)
            System.out.println("i1 == i2");
        else
            System.out.println("i1 != i2");
    }
}

答案:i1==i2  (数值在-128到127之间,自动装箱返回的是IntegerCache中的引用,因此是同一个对象)

public class AutoBoxDemo {
    public static void main(String[] args) {
        Integer i1 = 200;
        Integer i2 = 200;
        if (i1 == i2)
            System.out.println("i1 == i2");
        else
            System.out.println("i1 != i2");
    }
}

答案:i1!=i2 (数值超出-128到127这个范围,直接new一个对象返回,数值相等,但是对象不是同一个)

public class AutoBoxDemo {
    public static void main(String[] args) {
        Integer i1 = 200;
        Integer i2 = 200;
        if (i1.equals(i2))
            System.out.println("i1 == i2");
        else
            System.out.println("i1 != i2");
    }
}

答案:i1==i2 (数值超出-128到127这个范围,直接new一个对象返回,数值相等,用equals比较当然是相等的)

再次提醒:对象的相等性比较应该用equals,==进行的是同一性比较。

1 条评论:

  1. 类似的事情遇到过一次,
    先是NEW了一个dataset,然后赋给session.
    但是dataset有时是null的。
    结果在别的网页里拆箱时就出了错。
    虽说这样写的程序很烂,知识点懂得了一个

    回复删除