一、在讲解之前,我们先来看看Java jdk源码里边的一些关于equals方法:
1.这是Java基类Object里边的equals和hashCode方法,特殊的是这里比较的是两个对象是否相等;
public native int hashCode();
public boolean equals(Object obj) {
return (this == obj);
}
2.我们再来看看它下边的一些子类从写后的hashCode 和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;
}
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
char val[] = value;
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
hash = h;
}
return h;
}
②HashMap
class Node<K,V> implements Map.Entry<K,V> {
//其他代码省略
public final int hashCode() {
return Objects.hashCode(key) ^ Objects.hashCode(value);
}
public final boolean equals(Object o) {
if (o == this)
return true;
if (o instanceof Map.Entry) {
Map.Entry<?,?> e = (Map.Entry<?,?>)o;
if (Objects.equals(key, e.getKey()) &&
Objects.equals(value, e.getValue()))
return true;
}
return false;
}
}
我们就举两个列子吧,通过上边的代码我们可以发现重写后的equals到最后总是在比较两个对象里边的内容是否相等
一般情况下,只要有比较值相等的地方就一定有equals与hashCode。
**二、接下来我们详细讲解一下hashCode与equals方法**
①我们定义一个User类,不要重写equals与hashCode方法:
package equals;
public class User {
private String userName;
private String userpass;
public User(String userName, String userpass) {
super();
this.userName = userName;
this.userpass = userpass;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getUserpass() {
return userpass;
}
public void setUserpass(String userpass) {
this.userpass = userpass;
}
}
我们写一个测试方法先测试一下:(直接贴图了)
看一下结果:
我们可以看到在没有重写equals方法的时候,equals比较返回了false,还记得我们上边所说的Object’里边的equals比较的是两个对象是否相等,User没有从写相应的方法,所以说测试代码直接调用父类的方法进行比较,返回结果为false,相应的Set的size为2
②我们重写一下equals,不重写hashCode(给出代码片段)
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
User other = (User) obj;
if (userName == null) {
if (other.userName != null)
return false;
} else if (!userName.equals(other.userName))
return false;
if (userpass == null) {
if (other.userpass != null)
return false;
} else if (!userpass.equals(other.userpass))
return false;
return true;
}
好了,已经重写完毕,我们运行下上边测试代码,看结果:
equals方法返回了true,因为此时它调用自己的equals方法,所以比较对象的值是否相等,但是此时的Set里边的值还是两个(Set无序不可重复集合),在下一层究其原因:
③我们来重写hashCode与equals两个方法(给出代码片段):
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((userName == null) ? 0 : userName.hashCode());
result = prime * result
+ ((userpass == null) ? 0 : userpass.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
User other = (User) obj;
if (userName == null) {
if (other.userName != null)
return false;
} else if (!userName.equals(other.userName))
return false;
if (userpass == null) {
if (other.userpass != null)
return false;
} else if (!userpass.equals(other.userpass))
return false;
return true;
}
我们来在此运行测试代码看结果:
此时Set的大小已经变成了1,综合前一个案列,请注意,这里我们重写了hashCode方法,因此它比较了hashCode与equals两个方法,你们先自己体会一下,我们在下一层,做其他测试。
④这次我们重写hashCode方法而不重写equals方法:
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((userName == null) ? 0 : userName.hashCode());
result = prime * result
+ ((userpass == null) ? 0 : userpass.hashCode());
return result;
}
Ok,我们来测试运行一下:
此时的Set’里边的数据又变为了2,,在这里我们没有重写equals方法,所以equals方法返回的是false
⑤我们在来测试一个案列,测试代码如下:
public class Test {
public static void main(String[] args) {
//做一下简单测试,就不写泛型了,重点在理解equals与hashCode
ArrayList arrayList=new ArrayList();
Set set=new HashSet();
User user1=new User("张三", "asas");
User user2=new User("张三","asas" );
User user3=new User("啊哈哈","asas" );
User user4=new User("asas","asas" );
arrayList.add(user1);
arrayList.add(user2);
set.add(user1);
set.add(user2);
set.add(user3);
set.add(user4);
System.out.println(user1==user2);
System.out.println(user1.equals(user2));
System.out.println(arrayList.size());
System.out.println(set.size());
/**
* 我们来改变一下user4对象属性的值
*/
user4.setUserName("hahah");
//修改完之后尝试去删除
set.remove(user4);
/**
* 打印一下set集合大小
*/
System.out.println(set.size());
}
}
看一下运行结果:
我们看到Set的大小并没有变,主要是因为我们这里在将user4加入到Set集合中之后,在将其对应的属相的值进行修改在去删除的,结果没有删除成功。(原因:由于user4修改的username参与了hashCode的散列算法的计算,参与生成hash 码,因此将对应属性值修改后,对应的hash码被改变,导致要删除的对象没有被找到,所以说,我们在将 对象加入到集合中之后,一般不要修改其对应的属性值)
三、总结
为了解释以上案列情况,先给大家来一张流程图:这里所判断的hashCode与equals都是在判断当前集合是否含有相应的hash码与相应的值
通过流程图我们可以看出:
一般像集合(不可重复集合)里边加入数据时,它先会判断集合里边有没有相等的hashCode值,没有就直接加入集合;
如果有的话,他就会去判断集合里边是否有相对应的值,如果没有的话则加入集合,有的话则直接丢弃;
再给大家说一条(equals比较返回为true,则对应的hashCode一定相等,反之则不然,比如HashMap)
在上边的代码中我们看到,当只重写equals方法时,Set集合大小为2,加入了两个有相同值得对象(一般来说Set集合大小应该 为1),这是由于没有重写HashCode而导致的,由于在Set里边加入一个值时,先寻找相同的hash值,如果没有,则直接加入,这里就是由于没有重写hasdCode方法导致产生不同hash值,在集合中没有对应的hash值时直接将对象丢入set集合(HashMap集合也有类似的算法,大家可以去看看源码)
大家根据以上的总结可对代码在此进行回顾,相信已经说得很清楚了。这也更清楚的让我们知道,为何在重写equals方法时要重写hashCode。只不过hashCode方法的重写一般用于有集合对象参与时才使用。