hashCode()和equals()

本文深入探讨了Java中hashCode()和equals()方法的工作原理及重要性。解释了这两个方法之间的关系,提供了重写它们的指导原则,并展示了如何避免常见的陷阱。

个人对hashCode()和equals()方法的理解

前言:

很多java初学者在学习到容器的时候必定逃不开这俩东西,虽然教材上再三强调了equals相等时hashCode()一定相等,hashCode()相等时equals()不一定相等这句话,并且我们大多数情况下对于这个方法的理解也就是记住这句话。

本人第一遍学容器的时候关于这两个东西也是稀里糊涂,但是最近再次复习容器的时候,对这两个方法有了更加深刻的理解

关于equals():

如何理解”相等”这两字?
小学学数学的时候,老师是这样教的:

    定义a = 3,b = 3;
    那我们就说a与b相等。

可见“相等”并不是说两个数’本来’就是一个数,而是说我们给他们添加了一些规则,只要符合了这些规则,我们就认为他们相等

所以当我们重写equals()方法时,一定要记住的是我们是在给该’类’添加我们自己的规则,只要符合了我们自己定义的规则,我们就认为他们相等。(尽管你定义的规则看起来会很扯淡。。。)

//极端例子。。
class Human{
    private int id;
    private String name;
    private int sex;

    public boolean equals(Object o){    //我们定义的规则是:只要两个人性别相等那么就认为这两个人相等。
        return (o instanceof Human) && ((Human)o).sex == this.sex;
    }
}

多么扯淡的相等规则啊!如果按照此规则我和马云‘相等’(尬笑。。。)。但是就算规则在我们看来如此扯淡如此**,但对于java来说我和马云就是相等的。所以规则是我们自己定义的

不过规则也不是随便想怎么写就怎么写的,正确的equals()必须满足下列5个条件:

  1. 自反性:对任意x,x.equals(x)一定返回true
  2. 对称性:对任意x和y,如果y.equals(x)返回true,那么x.equals(y)也必然返回true。
  3. 传递性:对任意x,y,z。如果x.equals(y)返回true,y.equals(z)返回true,则x.equals(z)必然返回true
  4. 一致性:对于任意x和y,如果对象中用于等价比较的信息没有改变,那么无论调用x.equals(y)
    多少次,返回的结果应该保持一致
  5. 对任何不是null的x,x.equals(null)一定返回false。
    (我和马云完全符合这五条(尬笑。。。))
当然玩笑归玩笑,equals()方法除了满足上述五条最基本的要求以外,更重要的是符合实际开发环境的需要,千万不可随便定义规则。

关于hashCode():

其实这个才是本篇博客的重点。

首先介绍基本概念:

  • hashCode翻译过来叫散列码
  • hashCode返回的是一个int,可以将这个数理解成int版本的equals(),也就是不完全版本的equals()
  • 使用散列的目的是快速的在一个Set/Map容器中找到相应的对象。说快速是因为可以根据哈希码直接定位到对象,只用做很少甚至不用做任何循环和查找的操作。
  • hashCode的本质和应用只是用来定位对象的,和判断对象是否相等与hashCode()是否唯一没有任何必然联系。

以Map(数组+链表)为例:

  • 往Map中存放一个key,value时,首先由hashCode()得到相应的散列码
  • 然后由相应的规则调整散列码的值直到散列码的值小于数组的容量(一般是取余操作)。
  • 将key,value放入下标为调整过后的散列码数组中。
  • 根据key.equals()来判断在这个“槽位”中的链表是不是有该key值了(Map规则key值只能有一个且不能为null)
  • 若有则更改对应的value,没有加入链表。

可以看出上述过程中,hashCode()只是用来得到相应的“槽位”的,而得到槽位之后和hashCode()就没有关系了,如果是按照取余的规则,那么一个“槽位”甚至是由不同的散列码给组成的(比如5对3取余是2,8对3取余也是2),然后在根据equals()来判定该key是否已经在此槽位中了。

所以再次理解equals相等时hashCode()一定相等,hashCode()相等时equals()不一定相等这句话就非常容易了,我们往Map中存放一个对象,是先根据hashCode()找到“大致位置”(槽位),然后在“大致位置”里面再次用equals()进行寻找。如果hashCode()不相等,那么“大致位置”就已经找错了,里面的任何元素都不会满足equals()的。

同时也应该理解了判断对象是否相等与hashCode()是否唯一没有任何必然联系。,hashCode()只是找了一个大致范围,没有必要说对象的hashCode()一定是唯一的。

最后:

  1. 如果要重写equals方法时,强烈建议甚至必须重写hashCode()方法。

    原因很简单:java规定equals()相等是hashCode()必须相等,而Object类中的两个方法都是和本对象的指针有关的,换句话说都是唯一的,如果只重写equals()方法,那么就会出现hashCode()不等而equals()相等的情况了,这样的话任何使用散列的数据结构(HashSet,HashMap,LinkedHashSet,LinkedHashMap)就无法正确的处理问题了。

    加上强烈建议是因为java语法并没有强制要求这个,还有就是对于不用散列的程序而言就没必要了(比如课后作业。。。)

  2. 设计hashCode()方法的原则:

    • 最重要的因素:无论何时,对同一个对象调用hashCode()都应该产生相同的值。所以如果hashCode()是依赖对象中易变的数据,那就要当心了,因为此数据一旦改变,hashCode()生成一个不同是散列码,相当于产生一个不同的键
    • 另外也不应该使hashCode()依赖与具有唯一性的对象信息,尤其是使用地址这个值,因为这样会产生equals定义的规则通过,但是hashCode()是两个差距非常大的值。 (同时该条也说明了散列码没必要是唯一的)

      例:如果String没有重写hashCode。
          那么  map.put(new String("123"),"aaaa");
                map.put(new String("123"),"bbbb");
      会添加两个'相同'的key值。
      
    • 好的hashCode()应该产生分布均匀的散列码
  3. 重写hashCode()和equals()的示范代码:
    ecplise右键 选Source (或者直接Alt+Shift+s)-> Generate hashCode() and equals() -> 选中想要生成散列码因子的成员变量 -> OK

在 Java 中,`hashCode()` `equals()` 是 `Object` 类中定义的两个方法,它们分别用于生成对象的哈希码值以及判断对象是否相等。这两个方法之间存在紧密的契约关系,合理使用重写它们对于程序的正确性性能至关重要。 ### `equals()` 方法的作用 `equals()` 方法用于比较两个对象是否相等。在 `Object` 类中,默认的实现是基于对象引用地址进行比较的,即判断两个对象是否为内存中的同一实例。然而,对于自定义类,通常需要根据对象的实际属性来判断是否相等。例如,两个 `User` 对象如果具有相同的用户名密码,则可以认为它们是相等的,而不仅仅是引用相同的对象。 ### `hashCode()` 方法的作用 `hashCode()` 方法返回对象的哈希码值,该值是一个整数,用于快速标识对象。在 Java 中,哈希码主要用于哈希表(如 `HashMap`、`HashSet`)中,作为对象在哈希结构中的索引依据。默认情况下,`hashCode()` 返回的是对象的内存地址转换后的整数值。 ### 二者之间的关联性 在 Java 中,`equals()` `hashCode()` 方法之间存在一个重要的契约关系:**如果两个对象通过 `equals()` 方法判断为相等,那么它们的 `hashCode()` 返回值必须相同**。这一契约确保了在使用哈希结构时,能够正确地存储检索对象。 例如,在 `HashMap` 中,键对象的 `hashCode()` 被用来确定其在哈希表中的桶位置,而 `equals()` 则用于处理哈希冲突。如果两个对象相等但哈希码不同,那么即使它们通过 `equals()` 判断为相等,也可能被存储在不同的桶中,从而导致查找失败[^3]。 ### 为什么要一起重写? 当自定义类的对象需要作为哈希结构的键(如 `HashMap` 的键或 `HashSet` 的元素)时,必须同时重写 `equals()` `hashCode()` 方法。否则,可能会导致以下问题: - **对象无法正确检索**:如果两个对象逻辑上相等,但 `hashCode()` 返回不同的值,那么它们会被放入不同的桶中,无法通过 `equals()` 正确识别。 - **违反哈希结构的语义**:哈希结构依赖 `hashCode()` `equals()` 的一致性,否则可能导致集合行为异常,如重复元素无法检测或查找失败。 以下是一个典型的 `equals()` `hashCode()` 重写示例: ```java public class User { private String username; private String password; // 构造方法、getter setter 省略 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; if (!username.equals(user.username)) return false; return password.equals(user.password); } @Override public int hashCode() { int result = username.hashCode(); result = 31 * result + password.hashCode(); return result; } } ``` 上述代码中,`equals()` 方法根据 `username` `password` 判断对象是否相等,而 `hashCode()` 方法则基于相同的字段生成哈希码,确保了二者之间的一致性[^5]。 ### 总结 - `equals()` 方法用于判断两个对象是否相等。 - `hashCode()` 方法用于生成对象的哈希码,影响对象在哈希结构中的存储位置。 - 当两个对象通过 `equals()` 判断为相等时,它们的 `hashCode()` 必须返回相同的值。 - 在使用自定义对象作为哈希结构的键时,必须同时重写 `equals()` `hashCode()` 方法以确保正确性一致性[^4]。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值