hash 值重复_程序员:判断对象是否重复,不重写equals和hashcode不行吗?
前言
大家都知道如果要判断一个对象是否相同,都要在对象实体中重写equals和hashcode方法,那你知道为什么重写这两个方法就能根据自己定义的规则实现相等比较了吗?
今天带大家来了解一下equals和hashcode重写的实现。
set是如何去重的?
Set只是一个接口,我们平时使用最多的是HashSet,那么HashSet是如何去重的呢?
来看下是如何往set中添加一个对象的:
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
额,原来Set里其实是维护了一个map,通过map的key不可重复来实现我们想要的set功能。具体的map.put是怎么实现的呢?
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
我们都知道hashMap的基本原理是,通过hash去找数组的index位置,如果hash相等,那么相等的对象放到该hash槽的list中。如图所示:

再来看下putVal的源码是怎么实现的:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node[] tab; Node p; int n, i;
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
else {
Node e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode)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;
}
看到这里,我们就清楚了,key可以认为就是HashMap中的槽位,也就是整个tables数组,如果这个key在数组中不存在(通过hash来判断),如果不存在,那就将key放到这个槽位。如果存在(通过hash判断相等,这个时候已经鸠占鹊巢了,需要通过equals来判断了),key相等,就会被新key替换,然后对应的值会放到这个槽位后的list中。
自定义对象如何去重
看到刚才的分析,基本就比较清晰了,先通过hashcode判断,然后通过equals判断。默认这两个函数都是Object类中实现的
public native int hashCode();
public boolean equals(Object obj) {
return (this == obj);
}
Object中的hashCode是个native的方法,如果没有重写父类Object的hashCode方法,每次运行的结果都是不同整数,称为哈希值,没有特别意义;
而equals,默认的实现只是判断对象是否是同一个对象,这个明显也不是我们希望的。我们希望,对象的属性完全一样,就认为是相等的,无论这两个对象是否是同一个对象。
所以我们需要重写这两个方法,那怎么重写呢?
重写hashCode()
- 只要对象equals方法涉及到的关键域内容不改变,那么这个对象的hashCode总是返回相同的整数。(如果关键域内容改变,则hashCode返回的整数就可以改变)。
- 如果两个对象的equals(Object obj)方法是相等的,那么调用这两个对象中的任意一个对象的hashCode方法必须产生相同的整数结果。如果两个对象equals方法不同,那么必定返回不同的hashCode整数结果。(简而言之:相等的对象必须有相等的散列码即hashCode);
重写equals约定
- 自反性:x.equals(x) = true;
- 对称性:如果有x.equals(y) = true,那么一定有y.equals(x) = true;
- 传递性:对任意的x,y,z。如果有x.equals(y) = y.equals(z) = true,那么一定有x.equals(z)= true;
- 一致性:无论多少次调用,x.equals(y)总会返回相同的结果。
示例
假设我们现在有一个对象,里面包含两个属性,List和String:
public class User {
private List places = new ArrayList<>();
private String name;
}
重写及测试的方法如下:
public class User {
private List places = new ArrayList<>();
private String name;
public List getPlaces() {
return places;
}
public void setPlaces(List places) {
this.places = places;
}
public List addToPlaces(String place) {
this.places.add(place);
return this.places;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public boolean equals(Object object) {
if (object == this) {
return true;
}
if (object instanceof User) {
User other = (User)object;
if (!this.name.equals(other.name)) {
return false;
}
if (CollectionUtils.isEmpty(this.places) && CollectionUtils.isEmpty(other.places)) {
return true;
}
if (CollectionUtils.isEmpty(this.places) || CollectionUtils.isEmpty(other.places)) {
return false;
}
Collections.sort(other.places);
Collections.sort(this.places);
String origin = StringUtils.join(this.places.toArray(), ",");
String another = StringUtils.join(((User)object).places.toArray(), ",");
return origin.equals(another);
} else {
return false;
}
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + (name == null ? 0 : name.hashCode());
result = 31 * result + (places == null ? 0 : getListHashCode(places));
return result;
}
@Override
public String toString() {
return "name:" + this.name + " list:" + places;
}
private int getListHashCode(List list) {
if (list.size() == 0) {
return 0;
}
int hash = 0;
for (String s : list) {
hash += s == null ? 0 : s.hashCode();
}
return hash;
}
public static void main(String[] args) {
List userList = new ArrayList<>();
User user1 = new User();
user1.setName("li");
user1.addToPlaces("beijing").add("tianjin");
User user2 = new User();
user2.setName("li");
user2.addToPlaces("beijing").add("tianjin");
User user3 = new User();
user3.setName("zhang");
User user4 = new User();
user4.setName("zhang");
if (!userList.contains(user1)) {
userList.add(user1);
}
if (!userList.contains(user2)) {
userList.add(user2);
}
if (!userList.contains(user3)) {
userList.add(user3);
}
if (!userList.contains(user4)) {
userList.add(user4);
}
System.out.println(userList);
Set set = new HashSet<>();
set.add(user1);
set.add(user2);
set.add(user3);
set.add(user4);
System.out.println(set);
}
}
输出结果
[name:li list:[beijing, tianjin], name:zhang list:[]]
[name:zhang list:[], name:li list:[beijing, tianjin]]
总结
- equals方法用于比较对象的内容是否相等(覆盖以后)
- hashcode方法只有在集合中用到
- 当覆盖了equals方法时,比较对象是否相等将通过覆盖后的equals方法进行比较(判断对象的内容是否相等)。
- 将对象放入到集合中时,首先判断要放入对象的hashcode值与集合中的任意一个元素的hashcode值是否相等,如果不相等直接将该对象放入集合中。如果hashcode值相等,然后再通过equals方法判断要放入对象与集合中的任意一个对象是否相等,如果equals判断不相等,直接将该元素放入到集合中,否则不放入。
- 将元素放入集合的流程图:

- HashSet中add方法源代码:
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
- map.put源代码:
public V put(K key, V value) {
if (key == null)
return putForNullKey(value);
int hash = hash(key.hashCode());
int i = indexFor(hash, table.length);
for (Entry 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;
}
相关文章:

LazyT 延迟加载
namespace ConsoleAppTest {class Program{static void Main(string[] args){Lazy<Student> student new Lazy<Student>();//默认未初始化Console.WriteLine(student);//在第一次使用时才实例化Console.WriteLine(student.Value);Console.ReadLine();}public clas…

如何编写一个可升级的智能合约
链客,专为开发者而生,有问必答! 此文章来自区块链技术社区,未经允许拒绝转载。 如何编写一个可升级的智能合约 区块链信任基础的数据不可修改的特性,让它传统应用程序有一个很大的不同的地方是一经发布于区块链上就…

用ILSpy查看Session.SessionID的生成算法
缘由 asp.net Session在InProc模式下,容易丢失,经常需要重新登录,且不支持分布式共享。 所以在研究Redis实现原生的Session,本来想用GUID作为key存入cookie,又在想能不能实现跟Session一样的id 实现 ILSpy 是一个开源的.NET反…

java 中 bean 的生命周期
java 中 bean 的生命周期 本篇中会对涉及到的知识点皆做出描述: 首先,我们先了解先虚拟机的类加载机制: 虚拟机把描述类的数据从Class 文件中加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接…

python简易版实例_Python3之简单搭建自带服务器的实例讲解
WEB开发,我们先从搭建一个简单的服务器开始,Python自带服务模块,且python3相比于python2有很大不同, 在Python2.6版本里,/usr/bin/lib/python2.6/ 目录下会有 BaseHTTPServer.py, SimpleHTTPServer.py, CGIHTTPServer.…

如何选择分布式系统(区块链)协议?
链客,专为开发者而生,有问必答! 此文章来自区块链技术社区,未经允许拒绝转载。 如何选择分布式系统(区块链)协议? 在构建包分布式系统功能的应用程序时,《财富》500强企业和创始人经常问我们…

MySQL与IO
数据库作为存储系统,所有业务访问数据的操作都会转化为底层数据库系统的IO行为(缓存系统也可以当做是key-value的数据库),本文主要介绍访问mysql数据库的IO流程以及IO相关的参数。 一 MySQL 的文件 首先简单介绍一下MySQL的数据文件,MySQL 数据库包含如下…

python括号配对问题_使用Python的栈实现括号匹配算法
写一个栈的类:stack.py class Stack: def __init__(self): self.items [] def is_Empty(self): return self.items [] def push(self, item): self.items.append(item) def pop(self): return self.items.pop() def peek(self): return self.items[len(items)-1] …

万航单位换算器 V1.0 绿色版
软件名称: 万航单位换算器软件语言: 简体中文授权方式: 免费软件运行环境: Win 32位/64位软件大小: 347KB图片预览: 软件简介:万航单位换算器是一个可以随意转换单位的绿色软件,这个软件收集了各…

Golang学习-基础命令
链客,专为开发者而生,有问必答! 此文章来自区块链技术社区,未经允许拒绝转载。 、 Golang学习-基础命令 一、go run 用于运行命令源码文件,只能接收一个命令源码文件以及若干个库源码文件作为参数。先将源码文件编译…

js MD5加密处理
关于MD5: MD5.js是通过前台js加密的方式对用户信息,密码等私密信息进行加密处理的工具,也可称为插件。 在本案例中 可以看到MD5共有6种加密方法: 1, hex_md5(value) 2, b64_md5(value) 3, st…

手机qq2008触屏版_比微信老却是00后最爱 手机QQ 16年进化史
5月5日,腾讯QQ发布了《00后数据报告》,第一次以长图形式向公众展示了00后对于QQ的喜爱。当然物是人非,当年那个“胖企鹅”已经和现在功能强大、颜值超高的QQ不可同日而语。那些留存在我们记忆中,给我们带来无尽欢乐的聊天工具&…

密码学是如何保护区块链的
链客,专为开发者而生,有问必答! 此文章来自区块链技术社区,未经允许拒绝转载。 密码学是如何保护区块链的 摘要:密码学是应用数学函数以保证数据安全性的科学。 许多风靡的影视作品都在向人们暗示:只要有…

洛谷.4234.最小差值生成树(LCT)
题目链接 先将边排序,这样就可以按从小到大的顺序维护生成树,枚举到一条未连通的边就连上,已连通则(用当前更大的)替换掉路径上最小的边,这样一定不会更差。 每次构成树时更新答案。答案就是当前边减去生成树上最小边的权值。 LCT…

python数字计算公式_Python中数字以及算数运算符的相关使用
Python数字 数字数据类型用于存储数值。 他们是不可改变的数据类型,这意味着改变数字数据类型会分配一个新的对象。 当你指定一个值时,Number对象就会被创建: var1 1 var2 10 您也可以使用del语句删除一些对象引用。 del语句的语法是&#…

软件测试安全测试高峰论坛
Nubia测试以及介绍 基于Cucumber的自动化测试平台 常见Web漏洞之XSS,主要HTML与JS基础、XSS的基础知识与挖掘方法、XSS的利用 自动化测试框架以及测试思路 转载于:https://www.cnblogs.com/ITniu/p/5776005.html

以太坊是什么,为什么这么火?
链客,专为开发者而生,有问必答! 此文章来自区块链技术社区,未经允许拒绝转载。 以太坊是什么 以太坊(Ethereum)是一个建立在区块链技术之上, 去中心化应用平台。它允许任何人在平台中建立和使…

Python 把字符串变成浮点数
from functools import reducedi {}di.update(zip(1234567890., [1,2,3,4,5,6,7,8,9,0,.])) def str2float(s): st s.split(.) st1 reduce(lambda x,y: 10*x y, map(lambda x: di[x], st[0])) try: st2 reduce(lambda x,y: (x*0.1 y), map(lambda x:…

msbuild FileSysExcludeFiles
<?xml version"1.0" encoding"utf-8"?> <!-- This file is used by the publish/package process of your Web project. You can customize the behavior of this process by editing this MSBuild file. In order to learn more about this pl…

python二分法求解_Python使用二分法求平方根的简单示例
这篇文章主要为大家详细介绍了Python使用二分法求平方根的简单示例,具有一定的参考价值,可以用来参考一下。 对python这个高级语言感兴趣的小伙伴,下面一起跟随512笔记的小编两巴掌来看看吧! 使用二分法(Bisection Met…

智能合约语言Solidity Solidity API
链客,专为开发者而生,有问必答! 此文章来自区块链技术社区,未经允许拒绝转载。 智能合约语言Solidity Solidity API Solidity 是以太坊智能合约编程语言,阅读本文前,你应该对以太坊、智能合约有所了解&am…

PHP PSR-4 Autoloader 自动加载(中文版)
引用:https://segmentfault.com/a/1190000002521658 Autoloader 关键词 “必须”("MUST")、“一定不可/一定不能”("MUST NOT")、“需要”("REQUIRED")、“将会”("SHALL")、“不会”("SHALL NOT")、“应该”(&q…

236. Lowest Common Ancestor of a Binary Tree
原题链接:https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree/description/ 代码实现如下: import java.util.LinkedList; import java.util.Queue; import java.util.Stack;/*** Created by clearbug on 2018/2/26.*/ public clas…

python中append的用法_Python 列表 append() 使用方法及示例
Python 列表 append() 使用方法及示例 append()方法将一个项目添加到列表的末尾。 append()方法将单个项目添加到列表的末尾。 append()方法的语法为:list.append(item) append()参数 该方法有一个参数item -要添加到列表末尾的项目 该项目可以是数字,字…

Web3与智能合约交互实战
链客,专为开发者而生,有问必答! 此文章来自区块链技术社区,未经允许拒绝转载。 Web3与智能合约交互实战 以太坊中智能合约和web3交互实战 最近区块链、以太坊十分的火,所有就会有许多人去进入区块链这个工作&#x…

BZOJ 4595 SHOI2015 激光发生器 射线,线段,偏转
题目链接:http://www.lydsy.com/JudgeOnline/problem.php?id4595 题意概述: 给出一条射线和N条线段,射线遇到线段会发生反射,令入射角alpha,出射角beta,则betaalpha*phi_i(即对于每条线段phi是…

实现一个 能在O(1)时间复杂度 完成 Push、Pop、Min操作的 栈
一,问题描述 实现一个栈(元素遵守先入后出顺序),能够通过 min 方法在 O(1)时间内获取栈中的最小元素。同时,栈的基本操作:入栈(Push)、出栈(Pop),也是在O(1)时间内完成的。 二,问题分…

华为js面试题_四面腾讯与华为,大厂前端面试真BT!
今年算是经历颇多的一年了,腾讯和华为都走了几趟(一共面试了四个部门),拿了两个offer。(开心.png),但还是挂了两次,有点遗憾。面试题总结面试完之后,赶紧总结了一波&…

你和区块链的距离就差这篇文章!
链客,专为开发者而生,有问必答! 此文章来自区块链技术社区,未经允许拒绝转载。 近年来,“区块链”逐渐成为热门话题,2018年各种关于区块链的行业资讯、投融资创业、技术和应用探索等集中爆发,…

Browser Security-超文本标记语言(HTML)
重要的4个规则: 1 &符号不应该出现在HTML的大部分节点中。 2 尖括号<>是不应该出现在标签内的,除非为引号引用。 3 在text节点里面,<左尖括号有很大的危害。 4 引号在标签内可能有危害,具体危害取决于存在的位置&…