-
Notifications
You must be signed in to change notification settings - Fork 778
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
2019-06-17:谈谈如何重写equals()方法?为什么还要重写hashCode()? #77
Comments
先来说一下hashcode()和equals方法吧。 hashcode()
equals(Object obj)
我用一个简单的demo来举个例子吧. public class MyClass {
public static void main(String[] args) {
HashSet books=new HashSet();
books.add(new A());
books.add(new A());
books.add(new B());
books.add(new B());
books.add(new C());
books.add(new C());
System.out.println(books);
}
}
class A{
//类A的 equals 方法总是返回true,但没有重写其hashCode() 方法
@Override
public boolean equals(Object o) {
return true;
}
}
class B{
//类B 的hashCode() 方法总是返回1,但没有重写其equals()方法
@Override
public int hashCode() {
return 1;
}
}
class C{
public int hashCode(){
return 2;
}
@Override
public boolean equals(Object o) {
return true;
}
} 结果
|
往HashMap添加元素的时候,需要先定位到在数组的位置(hashCode方法)。 ) |
equals()相等的两个对象他们的hashCode()肯定相等,也就是用equals()对比是绝对可靠的。 hashCode()相等的两个对象他们的equal()不一定相等,也就是hashCode()不是绝对可靠的。 所以我们比较两个对象相等一般先比较hashcode(效率高,不复杂),再使用equal比较 |
equals相等的hasCode肯定相等,hasCode相等的equals不一定相等 |
equals:比较两个对象的地址是否相等 hashCode :一般用在集合里面,比如在hashMap 里面,存入元素的时候 会首先算出 哈希值,然后根据哈希值来确定元素的位置,对于在任何一个对象上调用hashCode 时,返回的 哈希值一定相等的。 为什么 需要重写 hashCode 给集合中存元素时,首先会获取 hashCode 的值,如果没有重写 hashCode ,他会直接将元素的地址转换成一个整数返回。如果我们创建了两个对象,两个对象的所有属性值都一样,在存入HashSet 时,第一个元素会直接存进去,第二个获取的 哈希值 和 第一个不同,所以第二个元素也会存进去,因为 jdk 默认不同的 hashCode 值,equals 一定返回false。所以 这两个值都会被存进去。但是这两个对象的属性值都是一样的,所以这样会造成数据的不唯一性。所以一般重写了 equals 后必须要重写 hashCode。 内存泄露的问题 想象一下,一个类 创建了两个对象,属性值不同,同时重写了 equals 和 hashCode 。然后将他们都存进了 HashSet 中。然后修改第二个 元素的值。最后将第二个元素充 set 集合中删除。 删除之后 则迭代进行打印,会发现第二个元素没有被删除掉,为什么呢? 因为在删除 某个元素时,会获取 hashCode 值,但是由于修改了属性值,导致获取的 哈希值和 存入时获取的不同,所以查找为空,jdk 认为该对象不在集合中,所以不会进行删除操作,但是用户任务 对象已经被删除,导致该对象长时间不能被释放,造成内存泄露。解决的办法是不要在执行的期间 修改与 HashCode 值相关的对象信息,如果非要修改,则必须先从集合中删除,更新数据后在添加到集合。 总结:
|
equals在内部调用"==",在不重写equals方法时,equals方法是比较两个对象是否具有相同的引用,即是否指向了同一个内存地址。而hashCode是一个本地方法,它返回的是这个对象的内存地址。 |
equals()方法比较的是对象的内存地址,而一般我们使用此方法意在比较两对象的在逻辑上是否相等,而不是两者的地址值,通常比较对象各个属性值是否相等,属性完全相等时在存储时,认为是一个对象,而hashcode有这样的规定:两个对象相等,hashCode一定相等。hashcode不等,两个对象一定不等。默认的hashcode 根据内存地址经过哈希算法实现的。比较两个对象,当重写的equals()计算两个对象完全相等,而两个对象的内存地址不相同,则计算得到的hashcode值不相等,出现矛盾。因此必须重写hashcode方法。 |
往HashMap添加元素的时候,需要先定位到在数组的位置(hashCode方法)。 |
一、为什么重写了equals要重写hashcode? 有这么一个场景,当用户登录时,来了两个user,有name和age,并且还有手机号,手机号相同的我认为是一个用户。那么我们很容易得到以下代码: import java.util.HashMap;
public class MockLogin {
static class User {
public int age;
public String name;
public String mobile;
public User( String mobile,int age, String name) {
this.age = age;
this.name = name;
this.mobile = mobile;
}
}
public static void main(String[] args) {
//小灰灰登录
User xhhFirstLogin = new User("123456",21, "xiaohuihui");
//这里我想记录小灰灰是否登陆过
HashMap<User,Boolean> map = new HashMap<>();
map.put(xhhFirstLogin,true);
//小灰灰第二次登陆来了,如果登陆过,我就把之前登陆信息给他,不再次登陆
User xhhSecondLogin = new User("123456",21, "xiaohuihui");
boolean b = map.containsKey(xhhSecondLogin);
//看是否登陆过
System.out.println(b?"登陆过":"没有登录");
}
} 但实际运行结果是: 没有登录 聪明的你一定看出来了,你没有重写equals啊,怎么判断手机号相等是同一个对象,好,那我们重写一下equals: public class MockLogin {
static class User {
public int age;
public String name;
public String mobile;
public User( String mobile,int age, String name) {
this.age = age;
this.name = name;
this.mobile = mobile;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(mobile, user.mobile);
}
}
public static void main(String[] args) {
//小灰灰登录
User xhhFirstLogin = new User("123456",21, "xiaohuihui");
//这里我想记录小灰灰是否登陆过
HashMap<User,Boolean> map = new HashMap<>();
map.put(xhhFirstLogin,true);
//小灰灰第二次登陆来了,如果登陆过,我就把之前登陆信息给他,不再次登陆
User xhhSecondLogin = new User("123456",21, "xiaohuihui");
boolean b = map.containsKey(xhhSecondLogin);
//看是否登陆过
System.out.println(b?"登陆过":"没有登录");
}
} 重写完之后的结果: 没有登录 还是没有登录。那么问题究竟出在哪里了呢?既然是containsKey返回的false,我们就去看看containsKey是怎么写的吧 public boolean containsKey(Object key) {
return getNode(key) != null;
} 接着往下找: /**
* Implements Map.get and related methods.
*
* @param key the key
* @return the node, or null if none
*/
final Node<K,V> getNode(Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n, hash; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & (hash = hash(key))]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null;
} 其实这里面只有一个行我们是需要注意的:
hash = hash(key));//根据key算出来的hash
if (first.hash == hash && //对比第一个节点的hash值
((k = first.key) == key || (key != null && key.equals(k)))) //如果key和第一个节点的key相同或者(key不等于空并且相等)
return first; 我们知道hashmap是一个拉链式的hash结构: containsKey是先找到hashcode,然后再来对比是否相等的。那么我们很容易得到猜想,是不是刚才hashcode不一样,导致的containsKey返回了false。我们先来打印一下hashcode: ....
boolean b = map.containsKey(xhhSecondLogin);
System.out.println("fcode = "+xhhFirstLogin.hashCode()+" scode = "+xhhSecondLogin.hashCode()); 看一下执行结果: fcode = 1704856573 scode = 705927765
没有登录 果然,是hashcode导致了我们containsKey函数的失败。那我们先简单的处理一下,让他们相等: public User( String mobile,int age, String name) {
this.age = age;
this.name = name;
this.mobile = mobile;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(mobile, user.mobile);
}
@Override
public int hashCode() {
return 1;
}
} 在user类增加hashcode方法,返回固定值1,我们再跑一下运行结果: fcode = 1 scode = 1
登陆过 这时真的相等了,当然hashcode = 1这是为了做测试,工程化落地时,我们还是用hashcode标准方法吧: @Override
public int hashCode() {
return Objects.hash(age, name, mobile);
} 我们认为,当他所有成员属性一样时,那就是一个对象,再次跑结果: fcode = -679329960 scode = -679329960
登陆过 技术总结:
贴一下最后的源代码: package com.android;
import java.util.HashMap;
import java.util.Objects;
public class MockLogin {
static class User {
public int age;
public String name;
public String mobile;
public User( String mobile,int age, String name) {
this.age = age;
this.name = name;
this.mobile = mobile;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return Objects.equals(mobile, user.mobile);
}
@Override
public int hashCode() {
return Objects.hash(age, name, mobile);
}
}
public static void main(String[] args) {
//小灰灰登录
User xhhFirstLogin = new User("123456",21, "xiaohuihui");
//这里我想记录小灰灰是否登陆过
HashMap<User,Boolean> map = new HashMap<>();
map.put(xhhFirstLogin,true);
//小灰灰第二次登陆来了,如果登陆过,我就把之前登陆信息给他,不再次登陆
User xhhSecondLogin = new User("123456",21, "xiaohuihui");
boolean b = map.containsKey(xhhSecondLogin);
System.out.println("fcode = "+xhhFirstLogin.hashCode()+" scode = "+xhhSecondLogin.hashCode());
//看是否登陆过
System.out.println(b?"登陆过":"没有登录");
}
} |
No description provided.
The text was updated successfully, but these errors were encountered: