Java中的装箱与拆箱

标签: java | 发表时间:2011-07-20 23:09 | 作者:DLevin senyo
出处:http://www.blogjava.net/

J2SE5.0后推出了自动装箱和拆箱的功能,以提高我们的开发效率,然而自动装箱和拆箱实际上是通过编译器来支持的(并非语言本身,或者说虚拟机),因而这种支持也隐藏了部分内部实质,再加上某些类的优化(比如Integer里面的缓存等,参看关于缓存节),就更加容易在特定的环境下产生问题,并且如果不知道原来还无法调试。以下先是简单的介绍了编译器对装箱和拆箱的实现,并根据实现简单介绍一下可能会遇到的几个问题。

装箱和拆箱实现

以下装箱和拆箱代码:

       Object value = 10;
       
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

查看以下代码:

       Integer value = null;
       
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函数。

对当前代码,我们可以一眼就看出当前valuenull的问题,但是如果这个null是在很远以外的地方赋值的呢?或者是间接赋值呢?这个时候遇到这种问题就会比较诡异了。

 

相等与不相等问题

查看一下代码:

       Integer value1 = 100;
       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方法对值在-128127之间的数值缓存了(参见关于缓存一节),因而value1value2的引用是相同的,而value3value4的引用是不一样的,而==比较的是引用,因而才会出现以上的结果。

这确的做法应该是:

       Integer value1 = 100;
       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 str1 = "abc";
       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

 

这是因为str1str2使用的是同一个字符串,即在字符常量中的字符串,而str3str4在使用字符常量中的字符为参数又创建出了两个新的字符串对象,因而在引用比较情况下是不等的。我们可以从字节码中得到这些信息(删除打印的代码):

     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方法,而不是使用“==”操作符。

 

关于缓存

据目前信息,有缓存的类有:ByteShortIntegerLong以及Boolean类。而这种缓存也只是在调用valueOf(静态)方法的时候才会存在(装箱正是调用了valueOf方法)。对整型,缓存的值都是-128127(包括-128127)之间,其他值都不缓存,而对Boolean类型只有truefalse值。代码如下:

public final class Integer extends Number {
    
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);
    }


2011-01-05

 

 



DLevin 2011-07-20 23:09 发表评论

相关 [java] 推荐:

Java中的锁(Locks in Java)

- - 并发编程网 - ifeve.com
原文链接 作者:Jakob Jenkov 译者:申章 校对:丁一. 锁像synchronized同步块一样,是一种线程同步机制,但比Java中的synchronized同步块更复杂. 因为锁(以及其它更高级的线程同步机制)是由synchronized同步块的方式实现的,所以我们还不能完全摆脱synchronized关键字( 译者注:这说的是Java 5之前的情况).

Java PaaS 对决

- 呆瓜 - IBM developerWorks 中国 : 文档库
本文为 Java 开发人员比较了三种主要的 Platform as a Service (PaaS) 产品:Google App Engine for Java、Amazon Elastic Beanstalk 和 CloudBees RUN@Cloud. 它分析了每种服务独特的技术方法、优点以及缺点,而且还讨论了常见的解决方法.

Java浮点数

- d0ngd0ng - 译言-电脑/网络/数码科技
Thomas Wang, 2000年3月. Java浮点数的定义大体上遵守了二进制浮点运算标准(即IEEE 754标准). IEEE 754标准提供了浮点数无穷,负无穷,负零和非数字(Not a number,简称NaN)的定义. 在Java开发方面,这些东西经常被多数程序员混淆. 在本文中,我们将讨论计算这些特殊的浮点数相关的结果.

Qt——转战Java?

- - 博客 - 伯乐在线
编者按:事实上,在跨平台开发方面,Qt仍是最好的工具之一,无可厚非,但Qt目前没有得到任何主流移动操作系统的正式支持. 诺基亚的未来计划,定位非常模糊,这也是令很多第三方开发者感到失望,因此将导致诺基亚屡遭失败的原因. Qt的主要开发者之一Mirko Boehm在博客上强烈讽刺Nokia裁了Qt部门的决定,称其为“绝望之举”,而非“策略变更”.

java 验证码

- - ITeye博客
// 创建字体,字体的大小应该根据图片的高度来定. // 随机产生160条干扰线,使图象中的认证码不易被其它程序探测到. // randomCode用于保存随机产生的验证码,以便用户登录后进行验证. // 随机产生codeCount数字的验证码. // 得到随机产生的验证码数字. // 产生随机的颜色分量来构造颜色值,这样输出的每位数字的颜色值都将不同.

Java异常

- - CSDN博客推荐文章
“好的程序设计语言能够帮助程序员写出好程序,但是无论哪种语言都避免不了程序员写出坏的程序.                                                                                                                          ----《Java编程思想》.

java面试题

- - Java - 编程语言 - ITeye博客
 抽象就是忽略一个主题中与当前目标无关的那些方面,以便更充分地注意与当前目标有关的方面. 抽象并不打算了解全部问题,而只是选择其中的一部分,暂时不用部分细节. 抽象包括两个方面,一是过程抽象,二是数据抽象.  继承是一种联结类的层次模型,并且允许和鼓励类的重用,它提供了一种明确表述共性的方法. 对象的一个新类可以从现有的类中派生,这个过程称为类继承.

Java使用memcached

- - 互联网 - ITeye博客
首先到 http://danga.com/memcached下载memcached的windows版本和java客户端jar包,目前最新版本是memcached-1.2.1-win32.zip和java_memcached-release_1.6.zip,分别解压后即可. 然后是安装运行memcached服务器,我们将memcached-1.2.1-win32.zip解压后,进入其目录,然后运行如下命令:c:>;memcached.exe -d install
c:>memcached.exe -l 127.0.0.1 -m 32 -d start.

Java线程池

- - 企业架构 - ITeye博客
线程的使用在java中占有极其重要的地位,在jdk1.4极其之前的jdk版本中,关于线程池的使用是极其简陋的. 在jdk1.5之后这一情况有了很大的改观. Jdk1.5之后加入了java.util.concurrent包,这个包中主要介绍java中线程以及线程池的使用. 为我们在开发中处理线程的问题提供了非常大的帮助.

java 缩略图

- - 开源软件 - ITeye博客
文章说明:根据用户上传的图片按等比例生成相应的的缩略图,两小例笔记. 第一、java-image-scaling 开源小工具生成图片缩略图. Maven地址: http://mvnrepository.com/artifact/com.mortennobel/java-image-scaling/.