编程Java开发者

在Java中,equals()和hashCode()方法是如何实现和使用的?它们的不正确实现会带来什么后果?

用 Hintsage AI 助手通过面试

答案。

在Java中,equals()hashCode()方法对集合(如HashMap、HashSet等)的正确工作至关重要。这个问题常常被初学者忽视,但这些方法的合同违反会导致应用程序逻辑中出现难以发现的错误。

问题背景:

在Java中,所有类最初都继承自Object类的equals()hashCode()方法。默认情况下,equals()比较对象的引用(即它们在内存中的物理位置),而hashCode()返回每个对象的唯一代码。但是,对于用户自定义类,往往需要根据内容而不是引用来比较对象。

问题:

如果equals()hashCode()方法没有被重写或重写不当,那么这些对象在基于哈希的集合中可能会出现意外的行为。这会导致缺失元素、重复或查找错误。

解决方案:

总是一起重写两个方法,严格遵循合同:

  • 如果a.equals(b) == true,那么a.hashCode() == b.hashCode()
  • 如果a.equals(b) == false,那么对hashCode的要求是不必唯一的。

正确实现的示例:

public class Person { private final String name; private final int age; public Person(String name, int age) { this.name = name; this.age = age; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Person person = (Person) o; return age == person.age && Objects.equals(name, person.name); } @Override public int hashCode() { return Objects.hash(name, age); } }

关键特点:

  • equals()方法必须是自反的、对称的、传递的和一致的。
  • hashCode()方法在数据不变时必须返回相同的值。
  • 在重写的方法中,必须比较有意义的字段。

陷阱问题。

在将存储在HashSet中的类中,是否可以仅使用equals()而不使用hashCode()?

不可以。如果只重写了equals(),基于哈希的集合将无法正确识别对象的唯一性。HashSet首先比较hashCode,然后比较equals。

在equals()和hashCode()中是否必须使用类的所有字段?

不需要。仅需使用对类逻辑身份有意义的字段。例如,如果对象有一个内部的唯一标识符,则仅使用该标识符即可。

@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; User user = (User) o; return Objects.equals(id, user.id); }

可以在equals()中基于getter而不是直接字段吗?

通常可以,如果没有副作用并且getter是稳定的。但是有风险,即getter在不同的调用中返回不同的值——则行为将不可预测。

常见错误和反模式

  • 在重写equals()时不重写hashCode()
  • 在计算hashCode()时使用可变字段。
  • 忽视这两个方法之间的合同。

现实生活中的例子

消极案例

开发者实现了User类并仅定义了equals()方法,而忘记了hashCode()。在HashSet中添加和查找对象时,会出现重复和元素"丢失"的情况。

优点:

  • 代码最小化

缺点:

  • 集合工作被破坏
  • 应用程序中的模糊和难以捕捉的错误

积极案例

开发者严格按照合同实现了两个方法,仅在相等性和哈希逻辑中使用id。集合按预期工作,查找和存储正常。

优点:

  • 可预测的行为
  • 哈希集合的正确工作

缺点:

  • 在类变更时需要保持逻辑的最新状态