2019独角兽企业重金招聘Python工程师标准>>>

重写equals方法

在Java中Object类是一个具体类,但它设计的主要目的是为了扩展,所以它的所有非final方法,都被设计成可覆盖(override)的。但任何一个子类在覆盖这些方法时都应遵守一些通用约定,否则就会在使用中引起各种问题。

equals方法定义于Object类中,用于比较两个对象是否相等,说起比较相等我们也常用==符号来比较,但两者有什么区别呢?

equals方法与==的区别

一般来说==用于比较基本类型值是否相等,如:int、float等,或者用于比较对象引用地址是否相同(两个引用指向同一对象),而equals方法则由程序员自己来实现(JDK源码里的类是由JDK的开发者实现的,同样也是程序员自己实现的)来比较两个对象是否相等(强调一下,这里说的是相等而非相同)的。后者包含前者,即:使用==比较相同的对象equals方法一定返回true。

什么时候需要重写equals方法?

通常我们需要在代码中实现判断一个对象是否等于另外一个对象,或者需要将对象加入集合时,会需要使用equals方法来提供判断逻辑(集合中添加元素时会使用contains方法来判断添加对象是否已存在于集合中,内部调用的判断方法即为equals方法)。

equals方法的等价关系

重写equals方法看似很简单,但很许多方式会导致错误,并且造成严重后果,所以Java规范对对重写equals方法定义了一些约定(非强制,但应尽量遵守),即:equals方法需要实现等价关系(equivalence relation)。

  • 自反性(reflexive),对于任何非null的引用值x,x.equals(x)必须返回true。
  • 对称性(symmetric),对于任何非null的引用值x和y,当且仅当y.equals(x)返回true时,x.equals(y)必须返回true。
  • 传递性(transitive),对于任何非null的引用值x、y和z,如果x.equals(y)返回true且y.equals(z)也返回true,那么x.equals(z)也必须返回true。
  • 对于任何非null的引用值x,x.equals(null)必须返回false。 你当然可以无视这些约定,但当你发现你的程序表现不正常或者未达到预期的时候,你可以会很难找到失败的根源(出自《Effective Java》)。

重写equals方法的最佳实践

如果说上面的条目还不是很具体的话,下面通过一些示例来阐述上面的条目。 首先我们有一个Employee类和Manager类,包含几个域对象(属性)

public class Employee {private String name;private Double salary;private Date joinDate;// getter / setter ...
}public class Manager extends Employee {private Double bonus;// getter / setter ...
}

如果我们不重写equals方法

    @Testpublic void equals_1() {// 有两个Employee对象,我们假定如果姓名与薪资相等即认为两个对象相等Employee x = new Employee();x.setName("Jane");x.setSalary(3500.0);Employee y = new Employee();y.setName("Jane");y.setSalary(3500.0);// 此时我们没有重写equals方法,此时使用的equals方法由Object提供,只简单比较两个对象是否相同assertTrue(x.equals(x));assertFalse(x.equals(y));}

会看到对象x.equals(x)返回true而x.equals(y)返回false,而根据假定条件应返回true,所以Object里的equals方法显然不够用,我们需要自定义equals方法。 而在实现自定义equals方法时,第一条约定自反性,这一条很难无意识地违反这一条(如果违反了,你在向集合中添加元素时就会重复添加),但通常我们还是实现该约定,这通常是一种性能优化的方式(如果两个比较对象是同一个对象,就返回true,后面的比较逻辑就省略了)。

    @Overridepublic boolean equals(Object obj) {// 这里使用==显示判断比较对象是否是同一对象if (this == obj) {return true;}// 对于任何非null的引用值x,x.equals(null)必须返回falseif (obj == null) {return false;}// TODO 核心域比较return false;}

注意@Override注解,重写方法时务必加上该注解,IDE会帮我们检查是否是重写父类方法,否则可能实现的是重载方法(改变了方法签名),导致后面运行出错而找不到问题的原因。

上面实现了自反性,下面继续实现对称性

    @Overridepublic boolean equals(Object obj) {// 这里使用==显示判断比较对象是否是同一对象if (this == obj) {return true;}// 对于任何非null的引用值x,x.equals(null)必须返回falseif (obj == null) {return false;}// 通过 instanceof 判断比较对象类型是否合法if (!(obj instanceof Employee)) {return false;}// 对象类型强制转换,如果核心域比较相等,则返回true,否则返回false// 强制类型转换前,必须使用instanceof判断,避免代码抛出ClassCastException异常Employee other = (Employee) obj;return (this.name == other.name || (this.name != null && this.name.equals(other.name)))&& (this.salary == other.salary || (this.salary != null && this.salary.equals(other.salary)));}

测试代码证明equals方法实现了对称性

    @Testpublic void equals_2() {Employee x = new Employee();x.setName("Jane");x.setSalary(3500.0);Manager y = new Manager();y.setName("Jane");y.setSalary(3500.0);assertTrue(x.equals(y));assertTrue(y.equals(x));}

但在使用instanceof的时候需要注意,如果所有子类拥有统一的语义时使用instanceof 检查,如果要求比较目标类必须与当前类为同一类,可以使用this.getClass() == obj.getClass()来比较。

使用JDK7提供的工具类优化代码

我们在写equals方法时,经常需要判断属性值是否为空,非空时才比较目标对象的相同属性值是否相等,而在JDK8中提供了Objects的工具类,可以帮我们简化这部分代码

    @Overridepublic boolean equals(Object obj) {// 这里使用==显示判断比较对象是否是同一对象if (this == obj) {return true;}// 对于任何非null的引用值x,x.equals(null)必须返回falseif (obj == null) {return false;}// 通过 instanceof 判断比较对象类型是否合法if (!(obj instanceof Employee)) {return false;}// 对象类型强制转换,如果核心域比较相等,则返回true,否则返回falseEmployee other = (Employee) obj;// 如果两者相等,返回true(含两者皆空的情形),否则比较两者值是否相等return Objects.equals(this.name, other.name)&& Objects.equals(this.salary, other.salary);}

另外该类还提供了深度比较的方法deepEquals,对于属性为引用类型比较使用。

重写hashCode方法

通常来说,覆写equals方法时必须要覆写hashCode方法,但这是为什么呢?

HashCode(散列码)是什么?

首先来说一下HashCode是什么,HashCode中文翻译为哈希码或散列码,由哈希算法,将对象映射为一个整型数值。在Java中一般用于HashMap、HashSet、HashTable集合类中。

为什么重写equals方法同时需要重写hashCode方法?

上面说到HashMap等哈希类型集合对类,由于HashMap的底层存储结构为数组结构,每个元素又是一个链表,而数组的下标即为HashCode,所以相同HashCode的对象会被存放在同个链表中。所以如果重写equals方法而不重写hashCode方法时,就会导致将两个相等的对象(equals判断相等)加入HashMap时,因为返回不同的HashCode而分在了不同的哈希桶中,造成重复添加元素(同一个哈希桶会通过equals方法判断是否重复)。

    @Testpublic void hashCode_1() {Employee x = new Employee();x.setName("Jane");x.setSalary(3500.0);Employee y = new Employee();y.setName("Jane");y.setSalary(3500.0);// HashSet底层由HashMap实现HashSet<Employee> sets = new HashSet<>();sets.add(x);sets.add(y);assertEquals(2, sets.size());}

上述测试代码证明了这一点,预期添加两个相等对象,集合中应只有一个元素才对。

怎样编写一个好的hashCode方法?

相等的对象必须具有相等的HashCode,但反过来却不一定,因为存在哈希碰撞,通俗地说就是不同对象(也不相等),可能生成的HashCode是相同的,而发生哈希碰撞的几率则是由哈希算法决定的。一般来说发生哈希碰撞几率越大,性能就越差,所以一个好的hashCode方法因尽可能的减少哈希碰撞的几率。

业界并没有最佳的哈希码生成算法(没有最好,只有最合适),这里参考《Core Java》和《Effective Java》给出一个参考实现

    @Overridepublic int hashCode() {int r = 17;r = 31 * r + this.name.hashCode();r = 31 * r + this.salary.hashCode();return r;}

使用JDK7中提供的工具类优化

同样Objects类也提供了hashCode的工具方法,底层代码使用了Arrays类的hashCode生成方法

@Override
public int hashCode() {return Objects.hash(this.name, this.salary);
}

下面是Arrays类的hashCode方法代码

    public static int hashCode(Object a[]) {if (a == null)return 0;int result = 1;for (Object element : a)result = 31 * result + (element == null ? 0 : element.hashCode());return result;}

String类中的hashCode方法

    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;}

JDK中在编写hashCode方法时,大量使用了31这个魔法数字,据《Effective Java》描述该数字有一个很好的特性:用移位和减法代替乘法,可以得到更好的性能31 * i == (i << 5) - i

散列码的性能优化

通常不建议会被修改的属性参与HashCode计算(实际难以避免),因为这会引起HashCode的变化,对于已加入HashMap的对象,不会重新分配存储位置,而导致一些问题。

对于一些比较复杂的对象,其HashCode的计算是一件非常消耗资源的事,一个简单的办法就是对其HashCode进行缓存,比如在类中添加一个属性,记录该HashCode,HashCode可以在类初始化时生成,也可以在第一次调用hashCode方法时生成,这要视具体应用而定。但前提条件是参与计算HashCode的属性值不能修改。

结语

有很多约定不是强制的,但实际开发过程中却应尽量遵循,这些“最佳实践”会减少很多代码中潜在的Bug,或者提升代码性能。

参考资料

  • 《Core Java》
  • 《Effective Java》
  • 《编写高质量代码:改善Java程序的151个建议》

转载于:https://my.oschina.net/zhanglikun/blog/1921429

Java拾遗:001 - 重写 equals 和 hashCode 方法相关推荐

  1. Java重写equals和hashCode方法

    在日常程序中,我们习惯使用equals方法来比较两个对象,继承自Object的equals方法默认的比较两个对象的内存地址(String类重写了equals方法,比较字符串的内容).假如我们创建了两个 ...

  2. 重写equals()与hashCode()方法

    出自:http://blog.csdn.net/renfufei/article/details/16339351 Java语言是完全面向对象的,在java中,所有的对象都是继承于Object类.Oj ...

  3. 重写equals()和hashcode()方法详解

    本文将通过一个示例程序来深入讲解java的equals()方法 1.示例程序: package cn.galc.test;public class TestEquals {public static ...

  4. java 复写hashcode_java 重写equals()和hashCode()

    用户信息实体类: public class UserInfo{ private Integer userId; private String userName; private Integer uer ...

  5. 【Java基础】之深入讨论equals()和hashcode()方法

    这篇文章将详解介绍 Java 的 equals() 与 hashCode() 方法 我们知道 Object 类是类继承结构的基础,所以是每一个类的父类,所有的对象,包括数组,都实现了在 Object ...

  6. hash 值重复_程序员:判断对象是否重复,不重写equals和hashcode不行吗?

    前言 大家都知道如果要判断一个对象是否相同,都要在对象实体中重写equals和hashcode方法,那你知道为什么重写这两个方法就能根据自己定义的规则实现相等比较了吗? 今天带大家来了解一下equal ...

  7. Java 细品 重写equals方法 和 hashcode 方法

    前言 在我们需要比较对象是否相等时,我们往往需要采取重写equals方法和hashcode方法. 该篇,就是从比较对象的场景结合通过代码实例以及部分源码解读,去跟大家品一品这个重写equals方法和h ...

  8. Java基础提升篇:equals()与hashCode()方法详解

    概述 java.lang.Object类中有两个非常重要的方法: public boolean equals(Object obj) public int hashCode() Object类是类继承 ...

  9. Java:重写equals()和hashCode()

    http://blog.csdn.net/ansel13/article/details/5437486 很基础的东西就是由于没上心,三番五次地出错,这次好好总结下吧. 众所周之,String .Ma ...

最新文章

  1. 使用Google Closure DepsWriter生成JS依赖文件(二)
  2. 皮一皮:高考考了 692 分想当程序员的女生
  3. AutoFac使用方法总结:Part III
  4. stm32 usb 虚拟串口 相同_为什么说你要学习USB?(一)
  5. python时间计算_python计算两日期之间工作日时长
  6. 高效!Anchor DETR:旷视提出一种基于Transformer的目标检测神器!
  7. 自制胎教音乐---太阳当头照
  8. linux下关于程序性能和系统性能的工具、方法
  9. thinkphp5 php代码中如何确定文件的路径位置
  10. 最新HTML5财经直播聊天室喊单直播间系统源码 Redis缓存技术
  11. 桥水基金:对中国股市自上而下的分析
  12. JAVA面向对象 从0.5到1
  13. 在华为做测试员是一种什么体验?带你深入了解华为
  14. ObjectArx自定义实体入门(C++)及注意事项
  15. macOS配置MAVEN环境变量执行source .bash_profile报错.bash_profile: not valid in this context: /xxx/xxx
  16. 叭叭一下Servlet的虚拟路径的映射
  17. PTA单链表 - 20. 单值化(去重)
  18. Intel Xeon E5-4650 VS AMD Opteron 6380
  19. 【英国诺森比亚大学】流体与热能课题组招收全奖博士
  20. [人工智能-深度学习-59]:生成对抗网络GAN - 基本原理(图解、详解、通俗易懂)

热门文章

  1. Leetcode题解(20)
  2. Oracle安装步骤及PL/SQL Developer连接数据库
  3. javaSocket与C通信
  4. c++ 使用vs2010调用 win32api
  5. 每3位新码农中就有2个是单身?来自31000人的调查报告显示……
  6. Django 框架14: 缓存
  7. Visual Studio 2010 Ultimate敏捷功能特性(上)
  8. 解决Access denied for user #39;#39;@#39;localhost#39; to database #39;mysql#39;问题
  9. 使用DotNetCharting控件生成报表统计图总结
  10. “后 PC”时代,应用为王