4、== 与 equals的区别? hashCode 与 equals ?

1、基本概念理解

在Java中,==equals() 是用来比较两个对象是否相等的两种不同的方法,而 hashCode() 则是用来获取对象的哈希码,通常用于哈希表等数据结构中。

==equals()

  1. ==

    1. == 是Java中的一个操作符,用于比较两个对象的引用是否相同,也就是说,它检查两个对象是否是同一个实例。
    2. 对于原始数据类型(如int, double等),== 比较的是值。
    3. 对于对象,== 比较的是内存地址,即两个对象是否指向内存中的同一个位置。
  2. equals()

    1. equals() 是一个方法,定义在 Object 类中,用于比较两个对象的内容是否相等。
    2. 默认情况下,equals() 方法的行为与 == 相同,即比较对象的引用。但是,通常建议重写 equals() 方法,以便比较对象的属性值是否相同。
    3. 重写 equals() 时,应同时重写 hashCode() 方法,以保持一致性。

hashCode()equals()

  1. hashCode()

    1. hashCode()Object 类中的一个方法,用于返回对象的哈希码值,这是一个整数值。
    2. 哈希码通常用于哈希表(如 HashMapHashSet)中,以快速定位对象。
    3. 如果两个对象通过 equals() 方法比较为相等,那么它们必须具有相同的哈希码。但是,如果两个对象的哈希码相同,它们不一定相等。
  2. equals()的关系:

    1. 当你重写 equals() 方法以比较对象的属性值是否相同时,也应该重写 hashCode() 方法。
      1. 一致性:如果两个对象通过equals()方法比较是相等的,那么这两个对象调用hashCode()方法也必须产生相同的整数结果。这是为了保持equals()hashCode()的一致性。
      2. 散列集合的一致性:Java中的许多集合类,如HashMapHashSet等,都是基于哈希表实现的。这些集合依赖于hashCode()方法来确定对象存储的位置。如果两个对象相等(equals()返回true),但它们的哈希码不同,那么在HashMap中它们将被存储在不同的位置,这会导致集合的行为不符合预期。
      3. 性能:正确的hashCode()实现可以提高散列集合的性能。如果对象的相等性定义改变,但不更新hashCode()方法,那么即使对象实际上是相等的,它们也可能被存储在哈希表的不同位置,这会降低查找效率。
      4. 合同要求Object类中的equals()hashCode()方法是相关的,Java的Object类文档中明确指出,当重写equals()方法时,也应该重写hashCode()方法,以维持它们的一致性。
      5. 避免潜在的错误:如果不重写hashCode()方法,可能会导致一些依赖于哈希码的操作出现错误,比如对象的索引、查找和存储。

总结

  • == 检查两个对象是否是同一个实例。
  • equals() 可以被重写以比较对象的逻辑等价性,即属性值是否相同。
  • hashCode() 返回对象的哈希码,用于哈希表等数据结构中。
  • 当重写 equals() 时,应同时重写 hashCode(),以保持一致性。

2、== 和 equals() 的适用场景

  1. 使用 == 的场景
    1. 当我们想要比较的是两个原始数据类型的值时,比如两个整数或者两个字符,我们就用 ==。比如,你想知道你手里的两个苹果是不是一样大,你就直接比较它们的重量或大小。
  2. 使用 equals() 的场景
    1. 当我们比较的是两个对象,而我们关心的是它们的内容是否相同,而不是它们是不是同一个对象时,我们就用 equals()。这就像是比较两个苹果的味道和颜色,即使它们不是同一个树上长出来的,只要味道和颜色一样,我们就可以认为它们是相等的。

3、代码实现

重写 equals() 方法

public class Person {
    private String name;
    private int age;

    // 构造函数
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 重写 equals() 方法
    @Override
    public boolean equals(Object obj) {
        // 检查是否是同一个对象的引用
        if (this == obj) {
            return true;
        }
        // 检查是否是同一个类型
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }
        // 类型转换
        Person person = (Person) obj;
        // 比较属性
        return age == person.age && (name != null ? name.equals(person.name) : person.name == null);
    }

    // getters 和 setters
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

在这个例子中,equals() 方法首先检查是否是同一个对象的引用,然后检查传入的对象是否为空或者是否是同一个类的实例。如果是,它会将传入的对象转换为 Person 类型,并比较 nameage 属性。

重写 hashCode() 方法

import java.util.Objects;

public class Person {
    // ... 其他代码 ...

    // 重写 hashCode() 方法
    @Override
    public int hashCode() {
        // 使用 Objects 类的 hash 方法来计算哈希码
        return Objects.hash(name, age);
    }
}

hashCode() 方法中,我们使用了 Objects.hash() 方法,它接受多个参数,并返回这些参数的哈希码的组合。这样,如果 nameage 都相同,那么 hashCode() 也会返回相同的值。

总结

通过重写 equals()hashCode() 方法,我们可以确保 Person 类的对象可以根据它们的属性来比较相等性,并且在使用哈希表等数据结构时能够正确地工作。记住,当你重写 equals() 方法时,一定要重写 hashCode() 方法,以保持两者之间的一致性。

4、性能考虑

  1. 均匀分布

一个好的 hashCode() 实现应该能够使得对象的哈希码在可能的哈希值范围内均匀分布。这样可以减少哈希碰撞,即不同的对象产生相同哈希码的概率。

  1. 不可预测性

哈希码应该是不可预测的,这意味着它不应该依赖于对象的内存地址或者任何可以被轻易猜测的信息。这有助于防止恶意攻击者通过预测哈希码来进行拒绝服务攻击。

  1. 快速计算

hashCode() 方法应该快速计算,因为它会被频繁调用。一个计算成本高昂的哈希函数会降低整体性能。

  1. equals() 方法的一致性

如果两个对象通过 equals() 方法比较是相等的,那么它们的 hashCode() 也必须相等。这是非常重要的,因为如果违反了这一点,那么在 HashMapHashSet 中查找对象时可能会出现问题。

  1. 示例:一个简单的 hashCode() 实现

    1. import java.util.Objects;
      
      public class Person {
          private String name;
          private int age;
      
          // ... 构造函数、equals()、getters 和 setters ...
      
          @Override
          public int hashCode() {
              // Objects.hash() 是一个方便的方法,可以为多个参数生成一个一致的哈希码
              return Objects.hash(name, age);
          }
      }
      

在这个例子中,我们使用了 Objects.hash() 方法来生成哈希码。这个方法接受多个参数,并将它们的哈希码组合起来,生成一个单一的哈希码。如果 nameagenullObjects.hash() 也能妥善处理。

注意事项

  • 不要过度优化 hashCode() 方法。Java 的 Objects.hash() 方法已经足够好,适用于大多数情况。
  • 避免使用可能导致大量哈希碰撞的哈希函数,比如总是返回同一个值的哈希函数。
  • 如果对象的属性发生变化,而这些变化会影响到对象的哈希码,那么在修改这些属性后,不应该再使用对象的原始哈希码。

5、实际应用

public class User {
    private String username;
    private String encryptedPassword;

    // 构造函数
    public User(String username, String encryptedPassword) {
        this.username = username;
        this.encryptedPassword = encryptedPassword;
    }

    // equals() 方法
    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }
        User user = (User) obj;
        return username.equals(user.username);
    }

    // hashCode() 方法
    @Override
    public int hashCode() {
        return Objects.hash(username);
    }

    // getters 和 setters
    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getEncryptedPassword() {
        return encryptedPassword;
    }

    public void setEncryptedPassword(String encryptedPassword) {
        this.encryptedPassword = encryptedPassword;
    }
}

说明

  1. equals() 方法
    1. equals() 方法中,我们只比较 username,因为这是用户的唯一标识。我们不比较 encryptedPassword,因为即使两个用户有相同的密码,它们也可能不是同一个用户。
  2. hashCode() 方法
    1. hashCode() 方法中,我们只使用 username 来计算哈希码。这样,即使两个用户的用户名相同,它们也会生成相同的哈希码,从而在散列表中占用相同位置。
  3. 属性的不变性
    1. 一旦用户被创建,usernameencryptedPassword 通常不应该改变。如果需要改变,应该重新计算 hashCode()

6、问题解决能力

当遇到两个逻辑上相等的对象却有不一致的 hashCode() 值时,这通常意味着 equals()hashCode() 方法之间的约定没有被遵守。以下是调试和解决这个问题的步骤:

步骤 1:审查 equals() 方法

  • 确保 equals() 方法正确地实现了对象相等性的逻辑。检查是否有任何遗漏的属性或者错误的比较逻辑。

步骤 2:审查 hashCode() 方法

  • 确保 hashCode() 方法使用了与 equals() 相同的属性集来计算哈希码。
  • 检查是否有任何属性被错误地排除在哈希码计算之外,或者是否有任何不应该参与哈希码计算的属性被错误地包括进来。

步骤 3:测试 equals()hashCode() 的一致性

  • 创建两个逻辑上相等的对象实例,并检查它们是否满足 a.equals(b)a.hashCode() == b.hashCode()
  • 如果不满足,说明 hashCode() 方法需要调整。

步骤 4:使用调试工具

  • 使用IDE的调试工具,逐步执行 equals()hashCode() 方法,查看属性值和计算结果。
  • 检查是否有任何条件分支或逻辑错误导致不同的哈希码。

步骤 5:编写单元测试

  • 编写单元测试来验证 equals()hashCode() 方法的一致性。
  • 测试不同的对象组合,包括相等的对象和不相等的对象。

步骤 6:优化 hashCode() 方法

  • 如果发现 hashCode() 方法存在问题,根据 equals() 方法的实现来优化它。
  • 确保使用 Objects.hash() 或其他可靠的方法来生成哈希码。

步骤 7:复查和重构

  • 复查代码以确保没有逻辑错误。
  • 如果需要,重构代码以提高可读性和可维护性。

示例代码

假设我们有以下 Person 类:

public class Person {
    private String name;
    private int age;

    // equals() 和 hashCode() 方法...
}

如果我们发现两个 Person 对象逻辑上相等但 hashCode() 不一致,我们可以这样调试:

Person p1 = new Person("Alice", 30);
Person p2 = new Person("Alice", 30);

System.out.println("p1.equals(p2): " + p1.equals(p2)); // 应该输出 true
System.out.println("p1.hashCode() == p2.hashCode(): " + (p1.hashCode() == p2.hashCode())); // 应该输出 true

if (!p1.equals(p2) || p1.hashCode() != p2.hashCode()) {
    // 调试和优化 equals() 和 hashCode() 方法
}

通过以上步骤,你可以有效地调试和解决 equals()hashCode() 不一致的问题。记住,保持这两个方法之间的一致性对于使用散列集合的性能和正确性至关重要。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Java中,Object类中定义了hashCode()和equals()方法。hashCode()方法返回对象的散列码,equals()方法比较两个对象是否相等。如果你自定义了一个类,并且要在该类的实例之间进行比较和查找,那么你需要覆盖hashCode()和equals()方法。 重写hashCode()方法的步骤如下: 1. 声明一个整型变量result并初始化为一个非零的奇数。 2. 对于对象的每个关键域f,计算该域的哈希码c: a. 如果该域是基本类型,则计算Type.hashCode(f),其中Type是相应基本类型的包装类。 b. 如果该域是一个对象引用,并且该类的equals方法通过递归调用equals的方式比较这个域,则同样递归调用这个域的hashCode()方法。如果这个域的值为null,则返回0。 c. 如果该域是一个数组,则需要对数组的每个元素进行上述操作,可以使用Arrays.hashCode方法。 3. 将计算得到的哈希码c合并到result中,可以使用result = 31 * result + c的方式。 4. 返回result。 重写equals()方法的步骤如下: 1. 首先判断传入的对象是否与当前对象引用相同,如果是则返回true。 2. 判断传入的对象是否为null或者与当前对象的类不同,如果是则返回false。 3. 将传入对象转换为当前类的类型。 4. 对于对象的每个关键域f,检查该域在传入对象和当前对象中的值是否相等。如果所有的关键域都相等,则返回true;否则返回false。 注意:当你重写equals方法时,也应该重写hashCode方法,以便在将对象放入哈希表等数据结构中时能够正确地工作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叫我阿杰好了

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值