博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
细说equals()方法和hashCode()方法
阅读量:6876 次
发布时间:2019-06-26

本文共 5827 字,大约阅读时间需要 19 分钟。

一、前言

       对于这两个方法的研究,源于一道比较经典的面试题:“x.equals(y)==true;x,y可有不同的hashcode对吗?”,其实这道题的关键在于考我们对equals()方法和hashCode()方法的理解,网上看了不少文章,有说对的,也有说不对的。在我看来对也不对,具体原因,我们下面慢慢分析。

二、equals()方法

       equals()方法是Object中定义的方法,任何类都可以重写,但是需要遵循一定的规范,我们看一下Object中的默认实现

public boolean equals(Object obj) {        return (this == obj);    }复制代码

可以看到就是对两个对象的比较,也就是两个对象的地址值是否相等。然而有的时候,默认的比较方式并不能满足我们的需求,比如要我们判断两个学生对象是否是同一个,需要从学号、姓名等方面来比较,这时候就要重写equals()方法了,这个重写规则,JAVA是有明确的规范的:

2.1 自反性:x.equals(x)必须为true;2.2 对称性:x.equals(y)和y.equals(x)返回值必须相等;2.3 传递性:x.equals(y)为true,和y.equals(z)为true,那么x.equals(z)也必须为true;2.4 一致性:如果对象x和y在equals()中使用的信息没有改变,那么x.equals(y)的值始终不变;2.5 非null : x不是null,y是null,那么x.equals(y)必须为false。复制代码

三、hashCode()方法

       这是Android SDK中Object类的hashCode()方法实现:

/**     * Returns a hash code value for the object. This method is     * supported for the benefit of hash tables such as those provided by     * {@link java.util.HashMap}.     * 

*/ public int hashCode() { int lockWord = shadow$_monitor_; final int lockWordStateMask = 0xC0000000; // Top 2 bits. final int lockWordStateHash = 0x80000000; // Top 2 bits are value 2 (kStateHash). final int lockWordHashMask = 0x0FFFFFFF; // Low 28 bits. if ((lockWord & lockWordStateMask) == lockWordStateHash) { return lockWord & lockWordHashMask; } return System.identityHashCode(this); } public static native int identityHashCode(Object x);复制代码

通过注释我们可以得知一个很重要的信息,那就是hashCode()这个方法主要是为了更好的支持哈希表(如HashMap、HashSet、HashTable等)。说到这,我们有必要了解一下哈希表的存储原理了:

       当我们向哈希表(如HashMap、HashSet、HashTable等)插入一个object时,首先调用hashcode()方法获得该对象的哈希码,通过该哈希码可以直接定位object在哈希表中的位置(一般是哈希码对哈希表大小取余),如果该位置没有对象,那么直接将object插入到该位置;如果该位置有对象(可能有多个,通过链表实现),则调用equals()方法将这些对象与object比较,如果相等,则不需要保存object,否则,将该对象插入到该链表中。

       这也就解释了,为什么equals()相等,那么hashCode()必须相等。因为,如果两个对象的equals()方法返回true,则它们在哈希表中只应该出现一次;如果hashCode()不相等,那么它们会被散列到表中不同的位置,哈希表中不止出现一次。

       JAVA建议,如果我们重写了equals()方法,那么也要重写hashCode()方法,因为默认的hashCode()方法返回的是该对象内存中的地址。还是上面的例子,我们进行两个学生的比较,依据是学号和姓名,如果都相等,那么认为是同一个对象,有如下代码:

public class Student {    private int id;    private String name;    public int getId() {        return id;    }    public void setId(int id) {        this.id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }}public class Main {    public static void main(String[] args) {        Student student1 = new Student();        student1.setId(1);        student1.setName("name1");        Student student2 = new Student();        student2.setId(1);        student2.setName("name1");        System.out.println(student1.equals(student2));    }}复制代码

这时候返回的是false,肯定不是我们想要的,所以我们需要重写equals()方法,让它符合我们的业务需求,重写后的代码如下:

public class Student {    private int id;    private String name;    public int getId() {        return id;    }    public void setId(int id) {        this.id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    @Override    public boolean equals(Object o) {        if (this == o) return true;        if (o == null || getClass() != o.getClass()) return false;        Student student = (Student) o;        if (getId() != student.getId()) return false;        return getName() != null ? getName().equals(student.getName()) : student.getName() == null;    }public class Main {    public static void main(String[] args) {        Student student1 = new Student();        student1.setId(1);        student1.setName("name1");        Student student2 = new Student();        student2.setId(1);        student2.setName("name1");        System.out.println(student1.equals(student2));    }}}复制代码

通过重写equals()方法之后,打印结果变成了true,但是这样就完了吗?我们进行另外一种测试:

public class Main {    public static void main(String[] args) {        Student student1 = new Student();        student1.setId(1);        student1.setName("name1");        Student student2 = new Student();        student2.setId(1);        student2.setName("name1");        System.out.println(student1.equals(student2));        Set
set = new HashSet<>(); set.add(student1); set.add(student2); System.out.println("setSize: " + set.size()); }}复制代码

上面student1.equals(student2)返回的是true,setSize理应返回的结果是1才对,但是返回的确是2,这是因为它们的hashCode是不一样的,所以Set集合认为它们是两个不同的对象,因此添加了两次。这时候hashCode()就排上用场了,我们需要重写hashCode()方法,这就是为什么JDK说,如果重写了equals()方法,必须要重写hashCode()方法的原因,:

public class Student {    private int id;    private String name;    public int getId() {        return id;    }    public void setId(int id) {        this.id = id;    }    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    @Override    public boolean equals(Object o) {        if (this == o) return true;        if (o == null || getClass() != o.getClass()) return false;        Student student = (Student) o;        if (getId() != student.getId()) return false;        return getName() != null ? getName().equals(student.getName()) : student.getName() == null;    }    @Override    public int hashCode() {        int result = id;        result = 31 * result + name.hashCode();        return result;    }}复制代码

这样才算完事了。

hashCode()方法重写的一些原则

a.如果重写equals()方法,检查条件“两个对象通过equals()方法判断相等,那么它们的hashCode()也应该相等”是否成立,如果不成立,则重写hashCode()方法。b.hashCode()不能太简单,否则容易造成hash冲突;c.hashCode()不能太复杂,否则会影响性能。复制代码

但是一般来说我们不需要自己去写,这里有几种便捷的实现方式

(1) Google的Guava项目里有处理hashCode()和equals()的工具类com.google.common.base.Objects;(2) Apache Commons也有类似的工具类EqualsBuilder和HashCodeBuilder;(3) Java 7 也提供了工具类java.util.Objects;(4) 常用IDE都提供hashCode()和equals()的代码生成。复制代码

至此,我们已经可以解答最开始的问题:“x.equals(y)==true;x,y可有不同的hashcode对吗?”

严格来说,如果两个对象x.equals(y)==true,那么它们的hashCode()也要相等,如果不相等,当把它们添加到哈希表中时就会出现数据混乱(如重复添加);但是,由于hashCode()方法并不是强制实现的,所以,是可能存在不同的hashcode的。

三、总结

       如果想写出优秀的代码又不想踩坑就遵循官方的规范吧。

转载地址:http://ofgfl.baihongyu.com/

你可能感兴趣的文章
检测 ip 是否断开,并使用邮箱报警
查看>>
整理第一周学习C的知识点
查看>>
Spring Data JPA 实例查询
查看>>
ping多线程
查看>>
PMP每日一题
查看>>
python中struct.unpack的用法
查看>>
解决物理内存足够时VMware 提示物理内存不足。。。
查看>>
java socket常见异常
查看>>
Dubbo与Zookeeper、SpringMVC整合和使用
查看>>
Spring中的属性scope
查看>>
SpringApplication你不知道的那些事!
查看>>
为什么比别人办事效率慢?因为你没用这几款强大的搜索软件!
查看>>
linux菜鸟基础学习 (二) 中篇
查看>>
配置网络
查看>>
0021-使用JDBC向Kudu表插入中文字符-cast的秘密
查看>>
Kubernetes 1.14发布:对Windows节点的生产级支持、Kubectl更新与持久本地卷
查看>>
PHP获取未来七天的日期和星期
查看>>
web防火墙的开通和部署
查看>>
驰骋工作流引擎,表单引擎工作事务单元测试报告
查看>>
删除的文件如何恢复?详细方法介绍
查看>>