关于包装类中的"等等"

1、equals方法与"=="的区别

         Java中可以使用关系操作符"=="或者特殊方法equals()来比较两个对象或变量的相等关系。返回的都是布尔值结果。但是这两个东西常常让人头昏脑胀的,先来看看两段代码:
Java代码
//代码一:比较整形包装类对象的"=="关系
Integer n1=new Integer(1); 
Integer n2=new Integer(1); 
System.out.println(n1==n2);// 打印结果:false
//代码二:比较整形变量的"=="关系
int n3=1;
int n4=1; 
System.out.println(n3==n4); //打印结果: true 

两段代码的打印结果完全不同,这主要是因为Java对象和Java基本数据类型所定义的变量在内存中所存储的方式是完全不一样的。  

       Java对象存储使用到了栈和堆(详细内容见 《Java五种不同的数据存储环境 》) 。其中,对象的引用(相当与C中的指针,保存了对象内容所在的地址)存储在栈中,而对象的具体内容却存储在堆中。运行的时候通过栈中的地址直接找到堆中对象存储的具体内容。 但基本数据类型所定义的变量直接携带“值”存储于栈中,这样做的用意在于提高运行效率。

        "=="比较的都是栈中的数据 , 也就是比较对象的引用和变量的值。 这一点很重要。
       代码一中的n1和n2是对象,他们在堆中不同的存储位置上创建了两个不同的对象(虽然对象的内容一样,但存储的地址不一样)。因此n1和n2在栈中记录的数据自然是两个对象在堆中的地址。当然比较起来不一样。
       代码二中的n3和n4并非对象,而是变量,变量在栈中的内容存储的是变量的值(堆中是没有变量的居住环境的)。因此比较栈中的内容当然是一样的,都是1。下面这个图已经说的很清楚了。

            栈                 堆                            栈
                |---------------|    |-------------|                 |---------------|
                |   n1: 0x1AD2 ---->    1     |                 |    n3:  1      |
                |---------------|    |------------- |                |---------------|

                |   n2: 0xB8DC ---->    1    |                 |    n4:  1      |
                |---------------|    |-------------|                 |---------------|
我们再看看equals行为:
//代码三:比较整形包装类的equals关系
Integer n1=new Integer(1);
Integer n2=new Integer(1);
System.out.println(n1.equals(n2));//打印结果:true 

//代码四:比较自定义类的equals关系
Value v1=new Value(1);
Value v2=new Value(1);
System.out.println(v1.equals(v2));//打印结果:false

先声明一点,变量是不存在方法行为。equals()方法只对对象有效。
     equals() 方法的默认行为 仍然是比较栈中的数据, 但要注意被子类覆盖的行为 。 但要注意:“默认行为 ”这几个字,换句话说:Java默认的equals()方法比较的仍然是对象引用,但OOP有一种重载特性,我们当然可以在自己的类中覆盖 equals()方法来改变这种行为。代码三中类Integer的equals()方法就覆盖了默认行为,这个方法比较的是对象的内容(具体实现可以看 JDK 1.5的源代码包)。代码四是自己编的一个类,没有重载equals()方法,这样v1.equals其实调用的是v1自动向上转型成万类之始的 Object类中的equals()方法,这就是我们所说的"默认行为"了。

2、String类型的常量池造成的相等问题。

  问题还是没有结束,下面两段代码更匪夷所思: 
Java代码
//代码五: 字符串的"=="和equals关系
String s1=new String("aaaa");
String s2=new String("aaaa");
System.out.println(s1==s2);//打印结果:false 
System.out.println(s1.equals(s2))//打印结果:true

//代码六:字符串的"=="和equals关系
String s3="aaaa";
String s4="aaaa";

System.out.println(s3==s4);//打印结果:true
System.out.println(s3.equals(s4))//打印结果:true  

String类型是并非基本数据类型,但它具有常量池这种特殊的存储结构。

  很显然,代码五中的s1和s2是对象引用,结果自然我们能理解了。但代码六中s3,s4的形式貌似基本数据类型的变量。但为什么具有 equals()方法行为呢?显然,s3,s4也不是基本数据类型,那"=="应该比较的是引用呀,怎么也true了呢?原来Java对String类型有一种特殊的关照,他会直接开辟一个String常量池,JVM会在String常量池中查找是否有"aaaa"这个字符串,有就返回,无就创建。当运行 String s3="aaaa";时,JVM会自动在String常量池中开辟一个空间用来存放“aaaa”,假设空间地址是0x5BC2。当运行到String s4="aaaa";JVM不会创建一个新的"aaaa",而是在String常量池中查找原有的"aaaa",再返回地址0x5BC2。因此s3,s4 仍然是引用,"=="比较的仍然是地址。但由于常量池的存在,这种存储方式使得相同字符串的地址是一样的(也就是说相同字符串只能有一个地址)。所以给我们很容易误解String的"=="比较的是内容了,其实不是的。

3、Integer类型的自动打包(autoboxing)功能造成的相等问题

     还有一种比较特殊的情况,请看下面两端代码:
Java代码
//代码七
Integer a=127;
Integer b=127;
System.out.println(a==b); //结果:true

//代码八
Integer c=128;
Integer d=128;
System.out.println(c==d); //结果:false

这里先阐述一下Integer a=127这样的语法形式。Integer是整形包装器类,127是int常量。编译器会调用Integer.valueOf(int)方法将int型常量自动打包(autoboxing)成包装器类。

      然后我们看看Integer.valueOf(int)的源代码:
Java代码
    /**
     * 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);
    }


    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);
        }
    }

看看Integer的源代码就知道了,其实Interger把-128—127之间的每一个值都建立了一个对应的Integer对象,并将这些对象组织成cache数组,类似一个缓存。 这个缓存数组中的Integer对象是可以安全复用的。也就是Integer a=127;和Integer b=127;中的引用a,b都是缓存数组中new Integer(127)对象的地址。所以代码七中的a==b自然是true

但要注意了,缓存数组只存储-128—127之间的Integer对象。对于其他的值的整形包装器,比如代码八中的Integer c,d=128分别在堆中创建了一个Integer对象用来存储128。两个引用的地址都不一样。

这里提一点:如果是Integer a=new Integer(127);这种常规形式创建的Integer是没有cache数组的。只有Integer a=127或Integer a=Integer.valueOf(127)这样的方式才能使用上cache数组。而且包装器的整形值在-128—127之间。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值