首页 > 代码库 > java中‘==’和‘equals()’方法的有趣探索

java中‘==’和‘equals()’方法的有趣探索

这两天在看周志明的《深入理解java虚拟机》,受益颇多,根据书中的启示,对java中‘==’和‘equals()’方法的区别做了一些探索。

首先,为了更快地让进入状态,我们先来所几个判断题,例程如下,请判断各个System.out.println()输出的结果。

<pre name="code" class="java">	public static void main(String[] args) {
		Integer a =1;
		Integer b =2;
		Integer c =3;
		Integer d =3;
		Integer e =321;
		Integer f =321;
		Long g = 3l;
		System.out.println(c==d);
		System.out.println(e==f);
		System.out.println(c==(a+b));
		System.out.println(c.equals(a+b));
		System.out.println(g==(a+b));
		System.out.println(g.equals(a+b));
	}

输出结果如下:

true
false
true
true
true
false

之前在网上收到这样一些解释:

1.基本数据类型,也称原始数据类型。byte,short,char,int,long,float,double,boolean
应用双等号(==),比较的是他们的值。
2.复合数据类型(类),用(==)进行比较的时候,比较的是他们在内存中的存放地址,对于Integer、String、Long....等类,由于装箱的处理,比较的是值。
3.equals在Object类中的默认实现和==一样,对于Integer、String、Long....等类对equals进行了重写,故而仍然比较的是值,而不是内存存放地址。

下面我们来用上述解释去解释输出结果:其中解释2可以解释第1、3、5行结果输出,却解释补了第2行输出;解释3可以解释第4行输出,却解释不了第6行输出。下面我们逐步去解答问题。

问题一:为什么值为3两个Integer对象使用==比较时返回true,而同为321的Integer对象在==时却为false?

既然你对于Integer对象使用==比较的是对象在内存中的地址,那么我们可以看看编译出来的字节码是如何标识一个Integer变量的,是否这两个对象的符号引用是一样的呢?

使用jdk自带的javap工具将例程的class文件做了格式输出,截取如下片段:

        10: iconst_3
        11: invokestatic  #16                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        14: astore_3
        15: iconst_3
        16: invokestatic  #16                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
        19: astore        4
  上面的这段字节码指令对应的是java代码中的‘Integer c = 3; Integer d = 3;’这两条指令,iconst_1指将常量1压入操作数栈中,invokestatic  指令调用Integer类的valueOf(int i)方法,并将常量1作为其参数,将Integer.valueOf(1)作为结果放入局部变量表的第1个Slot(内存单位)。从字节码可以看出,在运行时,jvm会两次调用Integer.valueOf()方法去初始化c、d变量,那我们去看看Integer.valueOf()方法的实现。
public static Integer valueOf(int i)
    {
        if(!$assertionsDisabled && IntegerCache.high < 127)
            throw new AssertionError();
        if(i >= -128 && i <= IntegerCache.high)
            return IntegerCache.cache[i + 128];
        else
            return new Integer(i);
    }
$assertionsDisabled变量字面理解应当断言不可用,加!后应当是断言可用(有待考证)。IntegerCache.high < 127,反编译内部类IntegerCache代码:

    private static class IntegerCache
    {

        static final int low = -128;
        static final int high;
        static final Integer cache[];

        static 
        {
            int i = 127;
           <span style="color:#6633ff;"> String s = VM.getSavedProperty("java.lang.Integer.IntegerCache.high");</span>
            if(s != null)
            {
                int j = Integer.parseInt(s);
                j = Math.max(j, 127);
                i = Math.min(j, 2147483519);
            }
            high = i;
            cache = new Integer[(high - -128) + 1];
            int k = -128;
            for(int l = 0; l < cache.length; l++)
                cache[l] = new Integer(k++);

        }

        private IntegerCache()
        {
        }
    }

发现IntegerCache.high的取值依赖于‘java.lang.Integer.IntegerCache.high’jvm的参数设置,其取值区间为[127,2147483519],所以第一个if语句大部分情况会跳过,再看第二个if语句,当i在[-128,IntegerCache.high]区间时,将会使用混村cache中的值,这时候无论多少次调用Integer.valueOf(int i),只要传入的i是相同的,那么返回的对象引用就是一个,反之,则不是一个。那么可以猜想IntegerCache.high默认应当设置为127。这就解释了为什么值为3两个Integer对象使用==比较时返回true,而同为321的Integer对象在==时却为false。

试想我们将jvm的参数"java.lang.Integer.IntegerCache.high"调整到321,则321的==比较也应当返回true,所以很多面试题是缺少前提的~

结论:在jvm的默认设置下,值为[-128,127]的Integer对象如果使用valueOf()初始化(如:Integer a = 2; 或者 Integer b = Integer.valueOf("3"); )的话,则对象引用相同,==返回true,区间外的Integer对象不满足此规律;

    可以通过设置jvm参数"java.lang.Integer.IntegerCache.high"来改变区间的最大值;

    Short.java和Long.java的valueOf(long l)实现将区间写死为[-128,127];

    Double.java和Float.java的valueOf()方法均采用new新对象的方式实现,故上述规律不适用。

问题二、为什么value相同的Long和Integer对象使用==比较返回了false?

这个比较简单,我们去看Long.java和Integer.java的equals()方法实现。

Long.java的equals方法实现:

    public boolean equals(Object obj)
    {
        if(obj instanceof Long)
            return value =http://www.mamicode.com/= ((Long)obj).longValue();>

Integer.java的equals方法实现:

    public boolean equals(Object obj)
    {
        if(obj instanceof Integer)
            return value =http://www.mamicode.com/= ((Integer)obj).intValue();>
上述代码一目了然:equals方法不支持类型转换,故而例程中的最后一行输出为false。

     结论:基本类型对应的装箱类型均将equals方法重写,使得比较的是值,而非内存地址;

    但是equals方法重写后仍然不支持数据类型的转换。

在这里极力推荐大家去看看jvm的一些底层实现,之后对以前的理解会更深。


  

java中‘==’和‘equals()’方法的有趣探索