加入收藏 | 设为首页 | 会员中心 | 我要投稿 济南站长网 (https://www.0531zz.com/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 教程 > 正文

为何重写 equals方法的同时必须重写 hashcode技巧

发布时间:2021-11-18 16:19:33 所属栏目:教程 来源:互联网
导读:Object 类是所有类的父类,其 equals 方法比较的是两个对象的引用指向的地址,hashcode 是一个本地方法,返回的是对象地址值。他们都是通过比较地址来比较对象是否相等的。其实这两个方法本身并没有任何关联。 为何重写 equals方法的同时必须重写 hashcode方

Object 类是所有类的父类,其 equals 方法比较的是两个对象的引用指向的地址,hashcode 是一个本地方法,返回的是对象地址值。他们都是通过比较地址来比较对象是否相等的。其实这两个方法本身并没有任何关联。
 
为何重写 equals方法的同时必须重写 hashcode方法
可以这样理解:重写了 equals 方法,判断对象相等的业务逻辑就变了,类的设计者不希望通过比较内存地址来比较两个对象是否相等,而 hashcode 方法继续按照地址去比较也没有什么意义了,索性就跟着一起变吧。
 
还有一个原因来源于集合。下面慢慢说
 
举个例子:
 
在学校中,是通过学号来判断是不是这个人的。
 
下面代码中情景为学籍录入,学号 123 被指定给学生 Tom,学号 456 被指定给学生 Jerry,学号 123 被失误指定给 Lily。而在录入学籍的过程中是不应该出现学号一样的情况的。
 
根据情景需求是不能添加重复的对象,可以通过 HashSet 实现。
 
public class Test {
    public static void main(String[] args) {
        Student stu = new Student(123,"Tom");
        HashSet<Student> set = new HashSet<>();
        set.add(stu);
        set.add(new Student(456, "Jerry"));
        set.add(new Student(123, "Lily"));
        Iterator<Student> iterator = set.iterator();
        while (iterator.hasNext()) {
            Student student = iterator.next();
            System.out.println(student.getStuNum() + " --- " + student.getName());
        }
    }
};
 
class Student {
    private int stuNum;
    private String name;
    
    public Student(int stuNum,String name){
        this.stuNum = stuNum;
        this.name = name;
    }
    
    public int getStuNum() {
        return stuNum;
    }
 
    public String getName() {
        return name;
    }
 
    @Override
    public boolean equals(Object obj) {
        if(this==obj)
            return true;
        if(obj instanceof Student){
            if(this.getStuNum()==((Student)obj).getStuNum())
                return true;
        }
        return false;
    }
}
输出为:
 
123 --- Lily
456 --- Jerry
123 --- Tom
根据输出我们发现,再次将学号 123 指定给 Lily 居然成功了。到底哪里出了问题呢?
 
我们看一下 HashSet 的 add 方法:
 
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}
其实 HashSet 是通过 HashMap 实现的,由此我们追踪到 HashMap 的 put 方法:
 
public V put(K key, V value) {
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);
    }
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key);
    int i = indexFor(hash, table.length);
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
 
    modCount++;
    addEntry(hash, key, value, i);
    return null;
}
根据 key,也就是 HashSet 所要添加的对象,得到 hashcode,由 hashcode 做特定位运算得到 hash 码;
利用 hash 码定位找到数组下标,得到链表的链首;
遍历链表寻找有没有相同的 key,判断依据是 e.hash == hash && ((k = e.key) == key || key.equals(k))。在add Lily 的时候,由于重写了 equals 方法,遍历到 Tom 的时候第二个条件应该是 true;但是因为 hashcode 方法还是使用父类的,故而 Tom 和 Lily的 hashcode 不同也就是 hash 码不同,第一个条件为 false。这里得到两个对象是不同的所以 HashSet 添加 Lily 成功。
总结出来原因是没有重写 hashcode 方法,下面改造一下:
 
public class Test {
    public static void main(String[] args) {
        Student stu = new Student(123,"Tom");
        HashSet<Student> set = new HashSet<>();
        set.add(stu);
        set.add(new Student(456, "Jerry"));
        set.add(new Student(123, "Lily"));
        Iterator<Student> iterator = set.iterator();
        while (iterator.hasNext()) {
            Student student = iterator.next();
            System.out.println(student.getStuNum() + " --- " + student.getName());
        }
    }
    
};
 
class Student {
    private int stuNum;
    private String name;
    
    public Student(int stuNum,String name){
        this.stuNum = stuNum;
        this.name = name;
    }
    
    public int getStuNum() {
        return stuNum;
    }
 
    public String getName() {
        return name;
    }
 
    @Override
    public boolean equals(Object obj) {
        if(this==obj)
            return true;
        if(obj instanceof Student){
            if(this.getStuNum()==((Student)obj).getStuNum())
                return true;
        }
        return false;
    }
    
    @Override
    public int hashCode() {
        return getStuNum();
    }
}
输出:
 
456 --- Jerry
123 --- Tom
重写了 hashcode 方法返回学号。OK,大功告成。
 
有人可能会奇怪,e.hash == hash && ((k = e.key) == key || key.equals(k)) 这个条件是不是有点复杂了,我感觉只使用 equals 方法就可以了啊,为什么要多此一举去判断 hashcode 呢?
 
因为在 HashMap 的链表结构中遍历判断的时候,特定情况下重写的 equals 方法比较对象是否相等的业务逻辑比较复杂,循环下来更是影响查找效率。所以这里把 hashcode 的判断放在前面,只要 hashcode 不相等就玩儿完,不用再去调用复杂的 equals 了。很多程度地提升 HashMap 的使用效率。

(编辑:济南站长网)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    热点阅读