1,分析为什么要重写hashCode方法和equals方法:
//学生类
class Student{
String id;
//这里为了方便说明hashCode的重写,定义一个全局变量和构造方法
public Student(String id) {
}
}
//test类
public class Test {
public static void main(String[] args) {
HashSet<Student> set=new HashSet<>();
set.add(new Student("1"));
set.add(new Student("1"));
System.out.println(set.size());
}
}
//上述代码输出:2
//理解调用add方法过程:add方法-->put方法--->putVal方法(-->1,hash方法(-->hashCode方法)-->2,equals方法)
//回忆:上一篇博客中有说到对自定义类Student来说,调用hash方法后,实质是比较对象的地址,上述两个对象的地址不同,所以调用hash方法返回的int类型值不同,在执行putVal方法后会存储成功。所以此时集合中存储了两个对象,集合长度自然为2.
//猜想:我们看到上述对象存储时,其id值是相同的,一般来讲,我们会认为id相同则为一个人,但是在上边却存入两个,那怎么实现按照id值来存储学生,相同的id只存储一个呢?下边我们详细分析
//首先调用putVal方法时,先执行了hash方法,在执行hash方法过程中会调用hashCode方法。
String类中的hashCode方法:相同的value(即key)会有相同的返回值。
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;
}
//返回一个int类型的值,传入putVal方法,
HashMap中的putVal方法:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null) //由上篇博客可知此时获得一个存储位置下标。根据我们的猜想,如果相同的id,调用hash方法后返回的值相同,则会是同一个下标,第一次如果存入对象的话,第二次相同id的对象将不会存入了。怎样让相同的id返回相同的值?重写比较规则,即重写hashCode方法(用id调用)
tab[i] = newNode(hash, key, value, null);
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k)))) //第二次存储相同的id时会执行此行代码,怎么样实现不存储此时的id?由下述代码知,e != null时,执行下一个if语句,直接关掉方法,不存储。e的值什么时候非null?p.hash == hash是true;(key != null && key.equals(k))也返回true时。重写equals方法后(用id调用),key.equals(k)返回true,if条件成立。
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}
2,重写hashCode方法和equals方法:
class Student{
String id;
public Student(String id) {
}
@Override
public int hashCode() {
return id.hashCode();
}
//在重写了hashCode方法后,用id调用String类中的hashCode方法,相同的id会返回相同的值,重复的id获得相同的下标,只存一次。
@Override
public boolean equals(Object obj) {
Student student=(Student)obj;
return id.equals(student.id);
}
//在重写了equals方法后,用id调用equals方法,相同的id会返回true,由前边分析将不会存储
}
如果泛型改为Object,在重写equals方法时要注意判断存入的对象是否是Object类,其子类,或其实现类,相应的重写equals的语句为
@Override
public boolean equals(Object obj) {
if(obj instanceof Object){
Student student=(Student)obj;
return id.equals(student.id);
}
}