今天看到两道题:
-
equals
和==
有什么区别? -
Integer a = 1; Integer b = 1; Integer a1 = 10000; Integer b1 = 10000; System.out.println(a == b); // true System.out.println(a1 == b1); //false
突然感觉对这一块的基础知识较为模糊,所以专门整理下这里的知识点,对JVM的操作数栈和字节码指令信息比较熟悉更容易理解下面的内容。
1. ==
网上的资料的解释:==
针对基础数据类型是直接比较值,而针对引用数据类型则是比较引用地址是否相同,题目2中用到的都是引用数据类型,为什么还会产生这样的区别呢?
JAVA代码编译后会形成字节码指令,所以可以先通过javap -v
来看一看能不能从字节码中看出问题所在:
整个程序的字节码指令过程如下:
- 加载操作数到操作数栈
- 通过
Integer.valueOf()
方法自动装箱操作 - 使用
if_acmpne
比较栈顶两个对象的引用是否相同
可以看到,两段程序除了加载操作数到操作数栈所使用的指令不同(JVM对于取值**-15**采用`iconst`指令,取值**-128127采用bipush
指令,取值-3276832767**采用`sipush`指令,取值**-21474836482147483647**采用 ldc
指令)外,其他的操作完全相同,所以唯一能够产生不同的地方就在于自动装箱操作了。
果然在Integer
的源码中发现了原因:
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
该方法会对-128~127的数据进行缓存,对于这一区间的数据返回的都是同一个对象,才导致了上面的不同。
扩展一下:
int a = 10000;
int b = 10000;
Integer c = 10000;
System.out.println(a == b);
System.out.println(a == c);
Map<Integer, Integer> map = new HashMap<>();
map.put(new Integer(10000), 1);
System.out.println(map.containsKey(new Integer(10000)));
还是通过javap -v
来看一看字节码
可以看到对于int
直接的比较使用了if_icmpe
指令对操作数直接进行比较,而对于int
和Integer
直接的比较则是通过对Integer调用Integer.intValue()
自动拆箱后再调用if_icmpe
指令,所以前两个结果都应该是true。
对于最后一个结果也是true,因为HashMap的containsKey()
是通过对象的hashCode判断的,而Integer对象的hashCode就是value本身。
2. equals()
上面提到==
仅可用于基本数据类型的值比较,如果想要对引用类型的值进行比较怎么办呢,可以使用equals()
,但是需要注意所使用的类型一定要覆写了Object类中的equals方法才能实现值比较:
Object类中的equals方法:
public boolean equals(Object obj) {
return (this == obj);
}
String中的equals方法:
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String anotherString = (String)anObject;
int n = value.length;
if (n == anotherString.value.length) {
char v1[] = value;
char v2[] = anotherString.value;
int i = 0;
while (n-- != 0) {
if (v1[i] != v2[i])
return false;
i++;
}
return true;
}
}
return false;
}