Object中hashcode(),equal()方法需要重写的时期

本文详细解析了Java中集合的基本概念,深入探讨了hashCode和equals方法的重要性及其在集合类如Set、HashMap中的实现机制。同时介绍了这两个方法的设计原则及注意事项。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Java中的集合(Collection)有两类,一类是List,再有一类是Set。 前者集合内的元素是有序的,元素可以重复;后者元素无序,但元素不可重复。

那么这里就有一个比较严重的问题了:要想保证元素不重复,可两个元素是否重复应该依据什么来判断呢? 这就是Object.equals方法了。但是,如果每增加一个元素就检查一次,那么当元素很多时,后添加到集合中的元素比较的次数就非常多了。

Java采用了哈希表的原理。哈希算法也称为散列算法,是将数据依特定算法直接指定到一个地址上。初学者可以这样理解,hashCode方法实际上返回的就是对象存储的物理地址(实际可能并不是)。   

这样一来,当集合要添加新的元素时,先调用这个元素的hashCode方法,就一下子能定位到它应该放置的物理位置上。 如果这个位置上没有元素,它就可以直接存储在这个位置上,不用再进行任何比较了;如果这个位置上已经有元素了, 就调用它的equals方法与新元素进行比较,相同的话就不存了,不相同就散列其它的地址。 所以这里存在一个冲突解决的问题。这样一来实际调用equals方法的次数就大大降低了,几乎只需要一两次。 

所以,Java对于eqauls方法和hashCode方法是这样规定的:

1、如果两个对象相同,那么它们的hashCode值一定要相同;

2、如果两个对象的hashCode相同,它们并不一定相同(上面说的对象相同指的是用eqauls方法比较。)  

你当然可以不按要求去做了,但你会发现,相同的对象可以出现在Set集合中。同时,增加新元素的效率会大大下降。

hashcode这个方法是用来鉴定2个对象是否相等的。 那你会说,不是还有equals这个方法吗? 不错,这2个方法都是用来判断2个对象是否相等的。但是他们是有区别的。 一般来讲,equals这个方法是给用户调用的,如果你想判断2个对象是否相等,你可以重写equals方法,然后在代码中调用,就可以判断他们是否相等 了。简单来讲,equals方法主要是用来判断从表面上看或者从内容上看,2个对象是不是相等。

举个例子,有个学生类,属性只有姓名和性别,那么我们可以 认为只要姓名和性别相等,那么就说这2个对象是相等的。 hashcode方法一般用户不会去调用,比如在hashmap中,由于key是不可以重复的,他在判断key是不是重复的时候就判断了hashcode 这个方法,而且也用到了equals方法。这里不可以重复是说equals和hashcode只要有一个不等就可以了!所以简单来讲,hashcode相 当于是一个对象的编码,就好像文件中的md5,他和equals不同就在于他返回的是int型的,比较起来不直观。我们一般在覆盖equals的同时也要 覆盖hashcode,让他们的逻辑一致。举个例子,还是刚刚的例子,如果姓名和性别相等就算2个对象相等的话,那么hashcode的方法也要返回姓名 的hashcode值加上性别的hashcode值,这样从逻辑上,他们就一致了。 要从物理上判断2个对象是否相等,用==就可以了。

在Java语言中,equals()和hashCode()两个函数的使用是紧密配合的,你要是自己设计其中一个,就要设计另外一个。在多数情况 下,这两个函数是不用考虑的,直接使用它们的默认设计就可以了。但是在一些情况下,这两个函数最好是自己设计,才能确保整个程序的正常运行。最常见的是当 一个对象被加入收集对象(collection object)时,这两个函数必须自己设计。更细化的定义是:如果你想将一个对象A放入另一个收集对象B里,或者使用这个对象A为查找一个元对象在收集对 象B里位置的钥匙,并支持是否容纳,删除收集对象B里的元对象这样的操作,那么,equals()和hashCode()函数必须开发者自己定义。其他情 况下,这两个函数是不需要定义的。

equals():

它是用于进行两个对象的比较的,是对象内容的比较,当然也能用于进行对象参阅值的比较。什么是对象参阅值的比较?就是两个参阅变量的值得比较,我们 都知道参阅变量的值其实就是一个数字,这个数字可以看成是鉴别不同对象的代号。两个对象参阅值的比较,就是两个数字的比较,两个代号的比较。这种比较是默 认的对象比较方式,在Object这个对象中,这种方式就已经设计好了。所以你也不用自己来重写,浪费不必要的时间。

对象内容的比较才是设计equals()的真正目的,Java语言对equals()的要求如下,这些要求是必须遵循的。否则,你就不该浪费时间:

•对称性:如果x.equals(y)返回是“true”,那么y.equals(x)也应该返回是“true”。

•反射性:x.equals(x)必须返回是“true”。

•类推性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也应该返回是“true”。

•还有一致性:如果x.equals(y)返回是“true”,只要x和y内容一直不变,不管你重复x.equals(y)多少次,返回都是“true”。

•任何情况下,x.equals(null),永远返回是“false”;x.equals(和x不同类型的对象)永远返回是“false”。

hashCode():
这个函数返回的就是一个用来进行赫希操作的整型代号,请不要把这个代号和前面所说的参阅变量所代表的代号弄混了。后者不仅仅是个代号还具有在内存中才查找对 象的位置的功能。hashCode()所返回的值是用来分类对象在一些特定的收集对象中的位置。这些对象是HashMap, Hashtable, HashSet,等等。这个函数和上面的equals()函数必须自己设计,用来协助HashMap, Hashtable, HashSet,等等对自己所收集的大量对象进行搜寻和定位。

这些收集对象究竟如何工作的,想象每个元对象hashCode是一个箱子的 编码,按照编码,每个元对象就是根据hashCode()提供的代号归入相应的箱子里。所有的箱子加起来就是一个HashSet,HashMap,或 Hashtable对象,我们需要寻找一个元对象时,先看它的代码,就是hashCode()返回的整型值,这样我们找到它所在的箱子,然后在箱子里,每 个元对象都拿出来一个个和我们要找的对象进行对比,如果两个对象的内容相等,我们的搜寻也就结束。这种操作需要两个重要的信息,一是对象的 hashCode(),还有一个是对象内容对比的结果。

hashCode()的返回值和equals()的关系如下:

•如果x.equals(y)返回“true”,那么x和y的hashCode()必须相等。

•如果x.equals(y)返回“false”,那么x和y的hashCode()有可能相等,也有可能不等。

为什么这两个规则是这样的,原因其实很简单,拿HashSet来说吧,HashSet可以拥有一个或更多的箱子,在同一个箱子中可以有一个 或更多的独特元对象(HashSet所容纳的必须是独特的元对象)。这个例子说明一个元对象可以和其他不同的元对象拥有相同的hashCode。但是一个 元对象只能和拥有同样内容的元对象相等。所以这两个规则必须成立。

设计这两个函数所要注意到的:
如果你设计的对象类型并不使用于收集性对象,那么没有必要自己再设计这两个函数的处理方式。这是正确的面向对象设计方法,任何用户一时用不到的功能,就先不要设计,以免给日后功能扩展带来麻烦。

如果你在设计时想别出心裁,不遵守以上的两套规则,那么劝你还是不要做这样想入非非的事。我还没有遇到过哪一个开发者和我说设计这两个函数要违背前面说的两个规则,我碰到这些违反规则的情况时,都是作为设计错误处理。

当一个对象类型作为收集型对象的元对象时,这个对象应该拥有自己处理equals(),和/或处理hashCode()的设计,而且要遵守前面所说 的两种原则。equals()先要查null和是否是同一类型。查同一类型是为了避免出现ClassCastException这样的异常给丢出来。查 null是为了避免出现NullPointerException这样的异常给丢出来。

如果你的对象里面容纳的数据过多,那么这两个函数 equals()和hashCode()将会变得效率低。如果对象中拥有无法serialized的数据,equals()有可能在操作中出现错误。想象 一个对象x,它的一个整型数据是transient型(不能被serialize成二进制数据流)。然而equals()和hashCode()都有依靠 这个整型数据,那么,这个对象在serialization之前和之后,是否一样?答案是不一样。因为serialization之前的整型数据是有效的 数据,在serialization之后,这个整型数据的值并没有存储下来,再重新由二进制数据流转换成对象后,两者(对象在serialization 之前和之后)的状态已经不同了。这也是要注意的。

<think>好的,我需要回答用户关于何时需要重写Java HashMap中的hashCodeequals方法的问题。首先,我得回忆一下HashMap的工作原理。HashMap通过键的哈希码来存储和检索值,所以当用户自定义对象作为键时,默认的hashCodeequals可能不符合需求。 用户提供的引用中有几个关键点。引用[1]和[2]提到,Object类的默认equals比较的是内存地址,而hashCode生成的是内存地址相关的值。如果两个逻辑上相等的对象因为地址不同而被视为不同,就会导致HashMap无法正确工作。比如,用户自定义的Key类如果没有重写这两个方法,即使id相同,HashMap也会认为它们是不同的键,导致重复插入,如引用[3]的例子。 接着,引用[4]和[5]强调,重写hashCode需要确保相等的对象有相同的哈希值,而equals则用于处理哈希冲突时的精确比较。如果只重写其中一个,HashMap可能出现无法检索到已存在的键或者存储重复键的情况。 现在需要将这些点整理成用户容易理解的回答。首先说明默认方法的局限性,然后解释何时需要重写:当自定义对象作为键,并且希望逻辑相等而非地址相等时。还要提到集合框架的使用场景,比如HashSet内部也依赖HashMap,同样需要重写这些方法。 还要注意示例和代码引用的正确性。比如引用[3]中的例子,虽然Key类重写hashCode,但没重写equals,导致containsKey检查失败,最终导致内存泄漏。这个例子很好地说明了问题。 最后,生成相关问题时要覆盖重写的原因、不重写的后果、方法之间的关系,以及实际应用中的例子。确保问题能帮助用户深入理解主题。</think>### 何时需要重写HashMap中的hashCodeequals方法? 在Java中,当**自定义对象作为HashMap的键**时,必须重写`hashCode`和`equals`方法以确保逻辑正确性。以下是具体场景和原因: --- #### 1. **默认方法的局限性** - **默认行为**: Object类的`equals`方法比较对象内存地址(`this == obj`),而`hashCode`生成与内存地址相关的哈希值[^1]。 - **问题**: 若两个对象逻辑相等但内存地址不同(例如两个`Key(id=5)`实例),默认方法会将其视为不同键,导致HashMap无法正确检索或覆盖值[^2]。 --- #### 2. **需要重写的场景** - **自定义对象作为键**: 例如,用`Student(id, name)`类作为键存储学生成绩。若需通过`id`判断相等性,则需重写`hashCode`和`equals`。 - **集合框架的使用**: `HashSet`、`HashMap`等依赖哈希表的集合,要求逻辑相等的对象必须具有相同的哈希值[^4]。 --- #### 3. **不重写的后果** - **哈希冲突导致数据重复**: 若仅重写`hashCode`而不重写`equals`,哈希冲突时可能无法正确比较对象(如引用[3]中的`Key`类),导致重复存储相同逻辑的键。 - **内存泄漏或性能问题**: 如引用[3]的示例中,未重写`equals`导致`containsKey`检查失败,HashMap会不断插入新键,最终内存溢出。 --- #### 4. **重写原则** - **一致性**: 若`a.equals(b)`为`true`,则`a.hashCode() == b.hashCode()`必须成立[^4]。 - **哈希分布均匀性**: `hashCode`应尽量减少哈希冲突,提升HashMap性能[^5]。 - **覆盖equals的逻辑**: 例如,通过对象的属性(如`id`或`name`)判断相等性。 --- #### 示例代码 ```java class Student { private int id; private String name; @Override public int hashCode() { return Objects.hash(id, name); // 基于属性生成哈希值 } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; Student other = (Student) obj; return id == other.id && Objects.equals(name, other.name); // 属性比较 } } ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值