这两天在看周志明的《深入理解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 == ((Long)obj).longValue(); else return false; }
Integer.java的equals方法实现:
public boolean equals(Object obj) { if(obj instanceof Integer) return value == ((Integer)obj).intValue(); else return false; }
上述代码一目了然:equals方法不支持类型转换,故而例程中的最后一行输出为false。
结论:基本类型对应的装箱类型均将equals方法重写,使得比较的是值,而非内存地址;
但是equals方法重写后仍然不支持数据类型的转换。
在这里极力推荐大家去看看jvm的一些底层实现,之后对以前的理解会更深。