equal()方法
equal()方法定义在Object内,所有的对象都继承了这个方法。
public boolean equals(Object obj) {
return (this == obj);
}
在Obejct源码内,equals方法使用==号来比较对象,==号是通过地址值来判断对象是否相等。
重写
Obejct 对象的equals方法有太多局限性,对于我们的String类,我们不希望通过地址值来判断是否相等,而是通过字面量来判断,虽然对于String类的对象,如果他们的地址值相等,他们的字面量也肯定相等。
判断以下代码
public static void main(String[] args) {
String a="tom";
String b=new String("tom");
System.out.println(a==b);
//false
}
结果是false,因为通过new去构建字符串它并不会去查询常量池,而是在堆内存里新建一个对象来存放字符串对象,这个时候我们就需要使用String 类重写的equals方法来进行比较。
public static void main(String[] args) {
String a="tom";
String b=new String("tom");
System.out.println(a.equals(b));
}
//true
我们来看看String类中equals源码
public boolean equals(Object anObject) {
if (this == anObject) {
return true;
}
if (anObject instanceof String) {
String aString = (String)anObject;
if (!COMPACT_STRINGS || this.coder == aString.coder) {
return StringLatin1.equals(value, aString.value);
}
}
return false;
}
可以看到重写方法并不是单纯比较字面量,首先比较两个对象地址,地址相同则两个对象和对象字面量肯定是相同的,地址不同再去比较字面量。
方法内部使用了强制转化 String aString = (String)anObject; 这使得该方法可以比较不同类型的对象。
在Java中有很多对象都重写了equals方法,比如Integer,Double这些包装类等。
hashcode()
hashcode()也是Object类中定义的方法,也被所有的类继承。
hashcode方法返回一个哈希值,该哈希值由哈希函数生成,哈希函数可以保证相同的对象有相同的哈希值,但不能保证不同的对象有不同的哈希值。
也就是说,如果两个对象相同,那么他们的哈希值肯定相同,两个对象的哈希值相同,但是对象可能不同。
我们把不同对象产生相同的哈希值称为哈希冲突,当样本足够大时哈希冲突很可能产生。
二者之间的关系
为什么重写了equals方法必须重写hashcode方法?
我们找到hashcode的定义
* Note that it is generally necessary to override the {@code hashCode}
* method whenever this method is overridden, so as to maintain the
* general contract for the {@code hashCode} method, which states
* that equal objects must have equal hash codes.
*
* @param obj the reference object with which to compare.
* @return {@code true} if this object is the same as the obj
* argument; {@code false} otherwise.
* @see #hashCode()
* @see java.util.HashMap
*/
public boolean equals(Object obj) {
return (this == obj);
}
大致意思就是如果重写了equals方法就必须要重写hashcode方法,以保证相同的对象有相同的哈希值,这个约定仅仅是口头约定,不执行也不会报错,不过会违反hashcode的定义。
不过我个人对此理解是为了实现对象对哈希表的支持。
两者的应用
hashmap是一个存储v键值的散列表,里面的元素都是不重复的,也就是说不存放相同的对象。它的底层实现是哈希表,而哈希表是利用对象的equals和hashcode方法来实现元素不重样的
哈希表是一个链表数组,在向哈希表存放元素时,会调用元素的hashcode方法,生成的哈希值又会映射到数组的下标,用来表示这个元素时存放在那个链表里面,我们使用hashSet来举例,往一个哈希表添加一个元素张三。
public static void main(String[] args) {
Set<String> strings=new HashSet<>();
String a="张三";
strings.add(a);
}
张三的哈希值假设被映射到了数组的0下标,该内存为空,则直接存放即可。
我们再添加一个新元素李四
public static void main(String[] args) {
Set<String> strings=new HashSet<>();
String a="张三";
String b="李四";
strings.add(a);
strings.add(b);
}
假设李四的哈希值为2,直接存放
再次存入一个元素,王五,王五的哈希值为0,与张三发生哈希冲突,jvm会调用王五的equals方法与张三比较,如果相同则直接覆盖,不相同则以张三为表头元素开启一个链表,王五存放在链表后面。
public static void main(String[] args) {
Set<String> strings=new HashSet<>();
String a="张三";
依此类推String b="李四";
String c="王五";
strings.add(a);
strings.add(b);
strings.add(c);
}
假设我们将张三再次存入哈希表,再发生哈希冲突后,jvm将张三和链表上所有元素比较,如果发现重复的则直接覆盖旧值,这就是哈希表存入元素不重样的原理。
哈希表的元素的查找也是一样的,先判断元素在那个链表上,再和链表上的元素比较,如果相同则查找成功。
由于哈希表独特的存储方式,所以他的查询和插入速度都非常快,相当于结合了链表和数组的优点。