@[TOC]谈谈equals与==的区别
首先,我们知道在比较基本类型的时候,用就可以解决,但是在比较对象的时候,就不能去使用了,而要去使用equals比较,那么为什么我们要这么做,那我们下面就一起来探索一下
==是如何比较数据的
1.基本数据类型
我们举例说明,如int a=3,首先他会在栈中存放一个a的引用,然后查找一个内存值为3的地,所以在进行==比较时,实际比较的是数值为3
2.类
A a=new A(); 首先会在堆空间上为a对象开辟内存,存放具体的对象数据,而具体的引用a会放在栈上,而a的实际内存值则为a对象的内存地址
所以如果比较的数据类型为基本数据时,==的比较为字面值的比较,而如果比较类型为实例对象时,比较的则是a的引用地址
equals是如何比较数据的
那么equals是父类Object的一个方法,所有的对象都会去继承Object类,那equals是如何比较的,那我们看一下Object的源码
public boolean equals(Object obj) {
return (this == obj);
}
可以看到,如果在不重写Object的equals方法时,equals与==没有区别,对象之间的比较,依然是内存值,但假如说两个数据为下面的情况时,就会发生不一样的情况
String a="a";
String b=a+"";
System.out.println(a==b);
//这里的输出结果为false
那造成这个原因的结果是,我们使用Unsafe方法来获取对应的内存地址,来看看,a与b是否还是指向的相同的地址
private static Unsafe getUnsafeInstance() throws SecurityException, NoSuchFieldException, IllegalArgumentException,
IllegalAccessException {
Field theUnsafeInstance = Unsafe.class.getDeclaredField("theUnsafe");
theUnsafeInstance.setAccessible(true);
return (Unsafe) theUnsafeInstance.get(Unsafe.class);
}
public static long addressOf(Object o) throws Exception {
Unsafe unsafe=getUnsafeInstance();
Object[] array = new Object[] { o };
long baseOffset = unsafe.arrayBaseOffset(Object[].class);
int addressSize = unsafe.addressSize();
long objectAddress;
switch (addressSize) {
case 4:
objectAddress = unsafe.getInt(array, baseOffset);
break;
case 8:
objectAddress = unsafe.getLong(array, baseOffset);
break;
default:
throw new Error("unsupported address size: " + addressSize);
}
return (objectAddress);
}
public static void main(String... args) throws Exception {
String a="a";
String b=a+"";
System.out.println("String:a " + a);
System.out.println("String:b " +b);
System.out.println("Addess:a " + addressOf(a));
System.out.println("Addess:b " + addressOf(b));
System.out.println(a==b);
}
最后的输出结果
String:a a
String:b a
Addess:a 3983463540
Addess:b 3983463711
false
可以看到,虽然a与b都是字符串a,但是他们所指向的内存地址已经发生了变化,
而如果 a与b同时设置字面量为“a”时,地址是相同的,因为在编译阶段,a和b指向字符串常量池同一个字符串常量
String a="a";
String b="a";
System.out.println("String:a " + a);
System.out.println("String:b " +b);
System.out.println("Addess:a " + addressOf(a));
System.out.println("Addess:b " + addressOf(b));
System.out.println(a==b);
//output
//String:a a
//String:b a
//Addess:a 3983463625
//Addess:b 3983463625
true
那么为什么String b=a+"";会造成指向的内存地址不一样呢?我们看String的源码可以知道
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence
String为字符串常量,是不可变对象,那么每次去做+号操作,实际是使用了StringBuffer的append方法,所以实际上在进行自+操作后,得到的是一个新的字符串常量,自然拥有不一样的内存地址。
那么为什么equals方法可以去比较字符串呢?我们查看一下String的源码
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;
}
可以看到,实际在比较的时候,是将字符串拆分成字符数组一一比较,所以,就算生成了新的字符串常量,只要字面值一样,也是相等的。
由此得出,基本类型在进行比较时,可以使用==,但是包装类型的比较,需要使用equals方法比较,因为包装类型的equals方法已经进行了重写,而equals没有重写,只能比较两个对象的内存地址是否一样,无法判断内容是否一样,需要重写equals和hashcode方法,来做比较规则编写。
为什么在重写equals方法需要重写hashcode
Java的编程规范中约定,相同对象必须有相同哈希值
那为什么需要有相同的hash值,我们从Object类的hashcode注释中得出结论
- 相同的对象,一定有相同的hash值
- 而不同的对象,则不一定,大概率的几率是不一样的,小概率会方法hash冲突
也就是,为该equals认定为相同的对象,提供相同的hash值
那为什么一定要相同的对象使用同样的hash值,就跟我们的集合hashMap、hashTable相关,要对hashmap的底层有一个了解
在hashMap中,放入一个对象,首先是根据对象的hash值查找元素,假如有相同的hash值,则进行元素覆盖,如果没有对应的hash值,则调用equals方法,判断对象是否相等,所以判断对象是否相等,在hashmap里,是要equals方法和hashcode同时判定的
所以在这里,我们假设String类只重写equals方法,不去重写hashcode
String a=“abc”;
String b=a+"";
那么如上述的两个变量,虽然equals方法相等,但是内存地址已经发生了变化,那么他们在往hashmap中存入时,会存入两个字面量相同的key,那么已经和hashmap的设计理念发生冲突,相同的key,只能存入一个键值对,
注:hashmap增删查改效率很高的原因是因为是查询和删除都可以根据hash值来定位元素
本文深入探讨了equals与==的区别。==在比较基本数据类型时比较字面值,比较实例对象时比较引用地址;equals在不重写时与==类似,重写后可比较内容。同时解释了重写equals方法时需重写hashcode的原因,与HashMap等集合的设计理念相关。

1019

被折叠的 条评论
为什么被折叠?



