在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。集合按预期工作,查找和存储正常。
优点:
缺点: