java自定义equals函数和hashCode函数
所有类都继承自Object类,他所有的非final方法:equals,hashCode, toString, clone 和 finalize,它们都有通用约定。 我们在覆盖这些方法的时候需要遵循这些约定,否则依赖这些约定的类(例如HashMap和HashSet)就无法结合该类一起工作了。
一. equals
相等的概念:
- 逻辑相等:例如Integer中包含的数值相等,我们就认为这两个Integer相等。 再比如AbstractList中如果两个list包含的所有元素相等则两个List相等。
- 真正意义上的相等:指向同一个对象。
如果不重载equals函数,那么两个类的相等只能是真正意义上的equal。如果类想要自己的相等逻辑就需要像Integer/List那样重载equals函数。
java规范中equals方法特征
- 自反性 : 对于任何非空引用x, x.equals(x) 返回true;
- 对称性: 对于任何引用x, y, 当且仅当y.equals(x) 返回true, x.equals(y)返回true;
- 传递性: 对于任何引用x, y, z, 若x.equals(y)返回true, y.equals(z)返回true; 则 x.equals(z)返回true;
- 一致性: 若x和y引用的对象没有发生改变, 则反复调用x.equals(y)应该返回同样的结果.
- 对任意非空引用x, x.equals(null) 返回false;
下面可以通过两个不同的情况看待这个问题:
如果子类能够拥有自己的相等概念, 则对称性需求强制采用getClass进行检测 如果由超类决定相等的概念, 那么就用instanceof进行检测,这样可以在不用子类的对象之间进行相等的比较
TimeStamp的不对称性
Date date = new Date();
Timestamp t1 = new Timestamp(date.getTime()); System.out.println("Date equals Timestamp ? : " + date.equals(t1));// true
System.out.println("Timestamp equals Date ? : " + t1.equals(date));// false
TimeStamp源码:(使用了instanceof 而不是 getClass())
// Timestamp
@Override
public boolean equals(java.lang.Object ts) {
if (ts instanceof Timestamp) {
return this.equals((Timestamp)ts);
} else {
return false;// 非Timestamp 实例直接返回false
}
}
// 省略其他代码
public boolean equals(Timestamp ts) {
if (super.equals(ts)) {
if (nanos == ts.nanos) {
return true;
} else {
return false;
}
} else {
return false;
}
}
父类Date:
// Date
@Override
public boolean equals(Object obj) {
return obj instanceof Date && getTime() == ((Date) obj).getTime();
}
备注:
- 在标准的java库中包含150多个equals方法的实现,包括instanceof检测, 调用getClass检测, 捕获ClassCastException检测或者什么都不做. 在java.sql.TimeStamp实现人员指出, Timestamp类继承Date类,而后者的equals方法使用了一个instanceof检测,这样重写equals方法时,就无法同时做到对称性.
- 在由超类决定相等时,可以考虑final关键字修改比较函数,若考虑到子类equals方法灵活性,可以不加修饰,例如AbstractSet.equals方法,应该申明为final, 这样就可以比较子类HashSet和TreeSet, 但是考虑到子类的灵活性,没有添加任何修饰.
编写equals方法的建议:
- 显示参数命名为otherObject, 稍后转化成other变量
public boolean equals(Object otherObject)
- 检测this和otherObject是否是同一个对象的引用,是,返回true;
if(this==otherObject){
return true;
} - 检测otherObject是否为null, 是, 返回false;
if(otherObject == null){
return false;
} - 比较this和otherObject是否属于同一个类. 如果equals的语义在每个子类中有所改变,就使用getClass检测:
if(getClass() != otherObject.getClass()){
return false;
}如果所以子类语义相同,使用instanceof检测:
if(!(otherObject instanceof Employee)){
return false;
} - 将otherObject转化为相对应的类型变量other
Employee other = (Employee)otherObject;
- 对所需要的比较的数据域进行比较. 如果是基本数据类型,使用a==b比较; 如果是对象比较,调用Objects.equals(a, b)进行比较
return Objects.equals(name, other.name) && salary == other.salary && Objects.equals(hireDay, other.hireDay);
二、hashCode()
设计原则中有一条: 覆盖equals时总要覆盖hashCode
hashCode编码原则:
1.只要对象equals方法的比较操作所用到的信息没有被修改,对同一对象调用多次,hashCode方法都必须返回同一整数。在同一应用程序的多次执行过程中,每次执行返回的整数可以不一致。
2.如果两个对象根据equals(Object)方法比较是相等的,那么这两个对象的hashCode返回值相同。
3.如果两个对象根据equals(Object)方法比较是不等的,那么这两个对象的hashCode返回值不一定不等,但是给不同的对象产生截然不同的整数结果,能提高散列表的性能。
具体实例
如果一个类覆盖了equals覆盖了equals函数,却没有覆盖hashCode会违反上述第二条原则。下面看一下没有重载hashCode的例子:
public class PhoneNumber {
private final int areaCode;
private final int prefix;
private final int lineNumber;
public PhoneNumber(int areaCode, int prefix, int lineNumber) {
rangeCheck(areaCode, 999, "area code");
rangeCheck(prefix, 999, "prefix");
rangeCheck(lineNumber, 9999, "line number");
this.areaCode = areaCode;
this.prefix = prefix;
this.lineNumber = lineNumber;
}
private static void rangeCheck(int arg, int max, String name) {
if(arg < 0 || arg > max) {
throw new IllegalArgumentException(name + ": " + arg);
}
}
@Override
public boolean equals(Object o) {
if(o == this)
return true;
if(!(o instanceof PhoneNumber))
return false;
PhoneNumber pn = (PhoneNumber)o;
return pn.lineNumber == lineNumber
&& pn.prefix == prefix
&& pn.areaCode == areaCode;
}
}
执行如下代码:
Map<PhoneNumber, String> map = new HashMap<PhoneNumber, String>();
map.put(new PhoneNumber(707, 867, 5309), "Jenny");
System.out.println(map.get(new PhoneNumber(707, 867, 5309)));
我们期望它返回Jenny,然而它返回的是null。
原因在于违反了hashCode的约定,由于PhoneNumber没有覆盖hashCode方法,导致两个相等的实例拥有不相等的散列码,put方法把电话号码对象放在一个散列桶中,get方法从另外一个散列桶中查找这个电话号码的所有者,显然是无法找到的。
只要覆盖hashCode并遵守约定,就能修正这个问题。
一个好的散列函数倾向于“为不相等的对象产生不相等的散列码”,下面有简单的解决办法:
1.把某个非零的常数值,如17,保存在一个名为result的int类型的变量中。(为了2.a中计算的散列值为0的初始域会影响到散列值)
2.对于对象中的每个关键域f,完成一下步骤:
a.为该域计算int类型的散列码c
i.如果该域是boolean,计算(f ? 1:0)
ii.如果该域是byte、char、short或者int类型,则计算(int)f
iii.如果该域是long,则计算(int)(f ^ (f >>> 32))
iv.如果该域是float,则计算Float.floatToIntBits(f)
v.如果该域是double,则计算Double.doubleToLongBits(f),然后
vi.如果该域是一个对象引用,并且该类的equals方法通过递归地调用equals的方式来比较这个域,则同样为这个域递归地调用hashCode。如果需要更复杂的比较,则为这个域计算一个“范式”,然后针对这个“范式”调用hashCode。如果域的值为null,则返回0(或其他某个常数,但通常为0)。
vii.如果该域是一个数组,则要吧每一个元素当做单独的域来处理,也就是要递归地应用上述规则,对每个重要的元素计算一个散列码,然后根据2.b把这些散列值组合起来。如果数组域中的每个元素都很重要,可以使用1.5中增加的其中一个Array.hashCode方法。
b.按照下面的公式,把步骤2.a中计算得到的散列码c合并到result中:
result = 31 * result + c。(选择31是因为它是一个奇素数,如果乘数是偶数,乘法溢出时会丢失信息,VM可以优化 31 * i == (i << 5) - i)
3.返回result。
编写完hashCode方法后,编写单元测试来验证相同的实例是否有相等的散列码。
把上面的解决方法应用到PhoneNumber类中:
@Override
public int hashCode() {
int result = 17;
result = 31 * result + areaCode;
result = 31 * result + prefix;
result = 31 * result + lineNumber;
return result;
}
现在使用之前的测试代码,发现能够返回Jenny了。
java自定义equals函数和hashCode函数的更多相关文章
- java类的equals()函数和hashCode()函数用法
以前总觉得java类对象很简单,但是今天的一个同事的点播,让我对java的对象有了不一样的理解,下面我来介绍一下equals()和hashCode()的用法: 先粘一段代码: public class ...
- JAVA 重写equals和重写hashCode
面试官可能会问你:“你重写过 hashcode 和 equals 么,为什么重写equals时必须重写hashCode方法?” 首先你需要了解: hashCode()的作用是获取哈希码(散列码) 它实 ...
- Java里 equals 和 == 以及 hashcode
本文探讨的是老掉牙的基础问题,先建个实体类 package com.demo.tools; public class User { private String name; public User(S ...
- java中equals相同,hashcode一定相同ma
一.jdk中equals和hashcode的定义和源码进行分析 1.java.lang.Object中对equals()方法的定义 java.lang.Object中对hashCode()方法的定义 ...
- JAVA中equals方法与hashCode方法学习
首先参考文章:http://www.oschina.net/translate/working-with-hashcode-and-equals-methods-in-java 1,equals方法的 ...
- java中equals方法和hashcode方法的区别和联系,以及为什么要重写这两个方法,不重写会怎样
一.在Object类中的定义为:public native int hashCode();是一个本地方法,返回的对象的地址值.但是,同样的思路,在String等封装类中对此方法进行了重写.方法调用得到 ...
- (转)Java中equals和==、hashcode的区别
背景:学习辉哥总结的基础知识,从头来,直面短板. 1 问题引入及分析 请看下面的代码清单1 @Test public void test01() { String a = "a" ...
- Java重写equals方法和hashCode方法
package com.ddy; public class User { private Integer id; private String name; private St ...
- JAVA正确地自定义比较对象---如何重写equals方法和hashCode方法
在实际应用中经常会比较两个对象是否相等,比如下面的Address类,它有两个属性:String province 和 String city. public class Address { priva ...
随机推荐
- JavaScript-----13.内置对象 Math()和Date()
1. 内置对象 js对象分为3种:自定义对象(var obj={}).内置对象.浏览器对象. 前两种对象是js基础内容,属于ECMAScript,第三个浏览器对象是js独有的.讲js API的时候会讲 ...
- python大作业二
一.存入csv 上次爬取到了所需要的内容,但是没有存入到csv中,这次存入了csv文件中,代码如下: import requests from bs4 import BeautifulSoup imp ...
- 剑指Offer-43.左旋转字符串(C++/Java)
题目: 汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果.对于一个给定的字符序列S,请你把其循环左移K位后的序列输出.例如,字符序列S=”abcX ...
- 【第二章】Zabbix3.4监控SQLServer数据库和H3C交换机思科Cisco防火墙交换机教程笔记
监控SQLServer数据库 SSMS执行相关SQL SQL模板命名规则 Zabbix客户端导入模板 添加SQLServer监控图形 SQLServer服务器关联模板 监控思科Cisco防火墙交换机 ...
- IT兄弟连 HTML5教程 CSS3揭秘 CSS3属性1
通过CSS选择器找到元素,就要使用CSS属性给找到的元素设置样式.尽管现在的浏览器已经支持了众多的CSS3属性,但作为初学者,最应该关注的就是一些“主流”的属性,如border-radius.box- ...
- C和C++常见误区以及问题整理
c和c++的关系 c是面向过程的语言,c++是在c的基础上扩展的面向对象的编程语言. c++具备c的所有功能,对c的库完全兼容. c++的标准在98年确定,在那之前已经有一些库大量使用. 新标准中,推 ...
- Java 的核心目的和并发编程
读一本书,最好能从它的前言开始.那么我们就来看看<Java编程思想>作者 Bruce Eckel 在前言里都说了些什么吧. 01.Java 的核心目的是"为程序员减少复杂性&qu ...
- Maven 教程之 pom.xml 详解
作者:dunwu https://github.com/dunwu/blog 推荐阅读(点击即可跳转阅读) 1. SpringBoot内容聚合 2. 面试题内容聚合 3. 设计模式内容聚合 4. My ...
- 痞子衡嵌入式:飞思卡尔i.MX RTyyyy系列MCU特性那些事(2)- RT1052DVL6性能实测(CoreMark)
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是飞思卡尔i.MX RTyyyy系列MCU的性能. 在前面的文章 i.MXRTyyyy微控制器概览 里,痞子衡给大家简介过恩智浦半导体在2 ...
- Web前端基础(18):jQuery基础(五)
1. 事件 前面我们介绍完js的事件流的概念之后,相信大家对事件流也有所了解了.那么接下来我们看一下jquery的事件操作. 在说jquery的每个事件之前,我们先来看一下事件对象 . 1.1 事件对 ...