java之自动装箱和拆箱(二)

代码如下:

public class EqualsDemo 
{
    public static void main(String[] args) {
        int i1=1,i2=1;
        Integer iv1=1,iv2=1;  
        Integer in1=new Integer(1),in2= new Integer(1);
        Integer it1=Integer.valueof(1),it2=Integer.valueof(1);
        print("(1) " + (i1==i2)); //(1)
        print("(2) " + (iv1==iv2)); //(2)
        print("(2) " + (iv1==it1)); //(2-1)
       print("(2) " + (it1==itv2)); //(2-2)
        print("(3) " + (in1==in2)); //(3)
        print("(4) " + (i1==iv1)); //(4)
        print("(5) " + (i1==in1)); //(5)
        print("(6) " + (iv1==in1)); //(6)
    } 
    static void print(Object obj){
        System.out.println(obj);
    }
}

  

输出结果:
(1) true
(2) true

(2-1)true

(2-2) true
(3) false
(4) true
(5) true
(6) false

(1) true:明白,==比较基本类型的值,i1、i2都是int类型,值相等。
(2) true:迷茫了。iv1、iv2是引用类型啊,这两个引用为什么会相等?这里没有显式地使用new,iv1和iv2到底是不是堆上的对象?如果是就不可能相等。如果不是,那它们存储在什么位置?
(3) false:明白,两个用new创建的在堆上的对象嘛。
(4) true:这个结果和(2)的结果让我totally confused. 一个引用类型和一个基本类型比较?比较的是什么?
(5) true:。。。
(6) false:两个引用类型比较。

总结了一下,我不明白的问题:
1、Integer这样的包装类的变量为什么可以直接赋值?
2、一个基本类型和其封装类型比较[参看(4)(5)]为什么会有诡异的结果?
3、i1==iv1,i1==in1的结果是真,为什么iv1==in1的结果就是假了呢?

是的是的是的!这一切的问题归结为autoboxing和autounboxing!!!

autoboxing和autounboxing是java5开始引入了机制,方便了基本类型和其对应的wrapper类型的转换。

autoboxing:可以直接把一个基本类型的值赋给其wrapper类型(所以Integer i=1;这样就不会有错了~),也可以把一个基本类型直接放入容器里(java5之前基本类型是不能放进容器中的),封装的过程由编译器来完成。

autounboxing:可以直接用wrapper类型变量给基本类型变量赋值,总之就是和autoboxing相反的过程,也是由编译器来完成的。

编译器是怎么完全封箱和拆箱的呢?
Integer i=1; 相当于 Integer i=Integer.valueOf(1);
valueOf(int):返回一个表示指定的int值的Integer实例。如果不需要新的Integer实例,则通常应优先使用该方法,而不是构造方法 Integer(int),因为该方法有可能通过缓存经常请求的值而显著提高空间和时间性能。[jdk api文档]
是的,缓存是关键,这能解释为什么(2)的结果是true。

Byte,Integer 和Long都是缓存了-128~+127之间的对象,autoboxing的时候,如果需要boxing的值在此范围之内,则直接返回缓存的对象,没有的时候再去new.

Boolean类型中直接缓存了两个Boolean对象,true和false,这样使用valueOf()方法时只需要直接返回这两个对象中的一个,而不是每次调用的时候都用new,这也就是文档里所说的通过缓存经常请求的值二显著提高空间和时间性能。

Character因为类型的特殊性,保存的是0-127之间的对象。

自动装箱不是直接调用new来完成的,要不然(2)应该是false。

拆箱则是调用了wrapper类的xxxValueOf()方法,所以记住此时不允许wrapper类的对象为null。

所以到这里就把(2)弄明白了。如果把所有的值都改成1000,输出的结果是:
(1) true
(2) false
(3) false
(4) true
(5) true
(6) false

发现(2)不再是true,这是因为Integer没有缓存1000这个值。(关于缓存,我应该看看享元模式,曾经看过,没弄懂...)

(4)的结果永远是true,我觉得是发生了autounboxing,变成了两个基本类型的比较,(5)同理。(6)是两个引用类型比较,没有发生autoboxing。

由于autoboxing和autounboxing的存在,会有一些奇怪的现象...
比如把(6)中的"=="改成"<="或">=",输出的结果就会是true,因为发生了autounboxing。
另外,(in1!=in2)&&(in1<=in2)&&(in1>=in2) 结果会是ture,同样是因为autounboxing。

 

总结一下:

java的autoboxing auto-unboxing
      java 5开始引入了autoboxing 和auto-unboxing机制,方便了基本类型和其对应的wrapper类型的转换。比如我们可以直接把一个基本类型的值赋给其wrapper类型,反之亦然;可以把一个基本类型直接放入容器里,封装的过程由编译器来完成;调用方法的时候可以直接匹配参数autoboxing和auto-unboxing之后的版本。
 这个过程编译器也只是做了个简单的处理,通过wrapper class的valueOf()方法对基本类型进行包装,通过wrapper class 的"基本类型名称"+Value() 方法得到其基本类型。
 比如
   Integer i=5;
int ii=i;
编译器将其变换为: 
 Integer i=Integer.valueOf(5);
   int ii=i.intValue();
对于其他基本类型是一致的,不过auto-unboxing使用的方法名要根据类型变换,比如boolean类型的Boolean.booleanValue(),byte的Byte.byteValue()等等。
  autoboxing的时候为什么使用valueOf() 方法而不使用new 来创建呢? 
  看文档的解释:
  Integer的valueOf()方法:因为该方法有可能通过缓存经常请求的值而显著提高空间和时间性能。
       
       注意两点:
      1.重点:缓存!(设计模式中的享元模式)
        Boolean类型中直接缓存了两个Boolean对象,TRUE和FALSE,这样使用valueOf()方法时只需要直接返回这两个对象中的一个,而不是每次调用的时候都用new,这也就是文档里所说的通过缓存经常请求的值二显著提高空间和时间性能。
        Byte,Integer 和Long都是缓存了-128~+127之间的对象,autoboxing的时候,如果需要boxing的值在此范围之内,则直接返回缓存的对象,没有的时候再去new.
        Character因为类型的特殊性,保存的是0-127之间的对象。
        举个实际的例子,这是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);
    }
    }
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);
    }

 知道了上面这点,Integer i1=1;Integer i2=1; (i1==i2)这个表达式为true的原因也就清楚了。
        2.“可能”
       文档里只说是可能,也就是说并不保证这一点,不同的类库实现起来可能是存在差别的。因此,比较引用所指向的对象的值时我们仍然要使用equals() 方法,比较引用是否相同时,使用==。

       还有一点比较有意思,<Java Puzzlers>一书里的一个例子:
       请提供i1和i2的声明,(i1!=i2)&&(i1<=i2)&&(i1>=i2)为true。
       看到i1<=i2 并且i1>=i2都成立,我们自然的想法就是i1==i2,但是java中的autoboxing和auto-unboxing破坏了这点。
       看这个声明:
      Integer i1=new Integer(0);
      Integer i2=new Integer(0);
      那么i1>=i2和i1<=i2都是true,因为使用>=和<=的时候会进行auto-unboxing操作,实际比较的是i1和i2auto-unboxing之后的基本类型值,因此这两个都是true,而i1和i2明显指向的不是同一个对象,所以i1!=i2也是true

 3:NullPointerException出现

例如: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错误。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值