前言

大家都知道如果要判断一个对象是否相同,都要在对象实体中重写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;

}

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

  1. springboot 主键重复导致数据重复_程序员:MySQL处理插入过程中主键或唯一键重复值的解决办法

    向MySQL插入数据有时会遇到主键重复的场景,原来的做法是先在程序代码中SELECT一下,判断是否存在指定主键或唯一键的数据,如果没有则插入,有的话则执行UPDATE操作,或另外一套逻辑,这种方法是不 ...

  2. Java程序员“找对象”攻虐

    嗯? 在后台经常会收到这样一类私信,大致是这样描述的: 看来关于「程序员找对象」这个话题,非常有必要用一篇文章来专门梳理和归纳一下了. 择日不如撞日,今天就把这件事情给安排了吧. 可以说,方法多得很! ...

  3. 程序员没对象,是不想找还是真的找不到?

    点击上方"程序人生",选择"置顶公众号" 第一时间关注程序猿(媛)身边的故事 图片源自:Humans Season 2 回答引自微博 编者按:「程序员找对象」已 ...

  4. 【源码+演示】高考加油!HTML+CSS特效文字祝福_程序员祝福高考学子旗开得胜!

    又是一年盛夏至,愿高三学子高中毕业日,即是高中名校时! 本篇为大家带来3款HTML+CSS制作出的小项目,为高考学子加油助威,愿他们旗开得胜,金榜题名![源码+演示]高考加油!HTML+CSS特效文字 ...

  5. python刷红包_程序员小伙使用Python刷短视频自动领红包,好嗨哟

    最近有几款比较火的刷短视频就可以领红包的App,只要简单的上下翻页浏览视频App就会自动计时累计红包,小编也快乐地参与到其中,由于浏览时间过长眼睛常常感觉到疲惫,手指也有点酸痛,根据多年的程序员思维, ...

  6. 为什么使用HashMap需要重写hashcode和equals方法_不同时重写equals和hashCode又会怎样?听听过来人的经验...

    可能一问到equals和hashCode相关的问题,就会有人讲他们的自反性,对称性,一致性,传递性等几条约定了,此时我不得不佩服,这么多约定竟然都能记得,但我不知道你是不是真的理解呢. 一.我不同时重 ...

  7. java浪漫代码_程序员表白代码,用过的人都找到了对象...

    在情人节送给自己的女朋友玫瑰花, 对于程序员来说是不是太普通了呢? 为什么不试试让情人节变得更特别一些呢? 作为一名程序员, 可以用自己的技术创造出不一样的浪漫! 让你的女朋友眼前一亮,印象深刻. 这 ...

  8. java 羽化_程序员羽化之路--假如需要一百万个对象

    设计背景 每个平台都会有用户这种基础数据的设计,作为最基础的用户,每个用户都有很多属性,比如性别,姓名,手机号等,每个用户还可以有类似经验值这样的荣誉系统,根据不同的经验值来对应不同的等级,不同的等级 ...

  9. object转成实体对象_程序员的浪漫,new一个对象

    程序员都喜欢 面向对象编程,但如果你没有对象怎么办?当然是 new 一个对象出来. 面向对象编程 是一种程序设计思想,而不是真正的面向你的对象去编程.如果你真的这么做了,恭喜你获得:你爱我还是爱电脑? ...

最新文章

  1. 周伯文对话斯坦福AI实验室负责人:下一个NLP前沿是什么?
  2. 低内阻的MOS管 4N04R7
  3. PCL1.8.1安装和一些小错误
  4. mysql释放练级_面试官:谈谈Mysql事务隔离级别?
  5. 【进展】温度监测报警器(系统)作品项目快发布了!
  6. vscode setting json_win10+letex+vscode+texlive+latex workshop+sumatrapdf
  7. JavaScript进阶1-学习笔记
  8. jsp中获取当前项目名称
  9. 蓝桥杯2015初赛-加法变乘法-枚举
  10. Linux USB 驱动开发实例(二)—— USB 鼠标驱动注解及测试
  11. 蚂蚁金服亿级并发下的移动端到端网络接入架构解析
  12. 单因素方差分析graphpad_python单因素方差分析实例
  13. java.lang.UnsupportedOperationException: null at java.util.AbstractList.add(AbstractList.java:148)
  14. 1006. 换个格式输出整数 (15)-PAT乙级真题
  15. 小乌龟解决反向线上代码冲突
  16. 精讲RestTemplate第6篇-文件上传下载与大文件流式下载
  17. python嵩天ppt_嵩天python课程笔记1
  18. python期货量化交易_基于vnpy搭建期货量化交易机器人(一)准备工作
  19. 拜腾“难”飞:全球化饮鸩止渴?
  20. 湖南交通学院校友会小程序云开发解决方案

热门文章

  1. Educational Codeforces Round 54
  2. Fedora dnf配置
  3. mysql事务处理用法与实例详解
  4. Spark Shuffle原理解析
  5. GoF23种设计模式之行为型模式之解释器模式
  6. 人工智能和机器学习领域有哪些有趣的开源项目
  7. Git fetch和git pull的区别
  8. AS3版本的MaxRects算法测试
  9. PL/SQL学习笔记-常量变量及数据类型初步
  10. Don’t Use the Win32 API PostThreadMessage() to Post Messages to UI Threads(翻译)