比较两个对象前,先决定比较的含义:是检查表单字段有没有变化,还是判断两个对象是否代表同一个业务身份?这两个问题可能需要不同的字段集合。
先选择参与比较的字段
值对象通常按内容比较;带持久化身份的实体可能按稳定标识比较。更新时间、缓存和派生字段不一定属于相等关系。不要因为工具能遍历所有字段,就让它们自动全部参与 equals。
下面用一个不可继承、字段不可变的值对象演示 Java 8 写法。这里只比较编号和名称,不处理任意继承树或循环对象图。
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;
public final class ItemValue {
private final int code;
private final String name;
public ItemValue(int code, String name) {
this.code = code;
this.name = name;
}
@Override
public boolean equals(Object other) {
if (this == other) return true;
if (other == null || getClass() != other.getClass()) return false;
ItemValue that = (ItemValue) other;
return code == that.code && Objects.equals(name, that.name);
}
@Override
public int hashCode() {
return Objects.hash(code, name);
}
public static void main(String[] args) {
ItemValue a = new ItemValue(1, null);
ItemValue b = new ItemValue(1, null);
Set<ItemValue> values = new HashSet<>();
values.add(a);
System.out.println(a.equals(b)); // true
System.out.println(a.equals(null)); // false
System.out.println(values.contains(b)); // true
}
}
Objects.equals 对 null 友好;基本类型使用合适的值比较。数组需要单独选择 Arrays.equals 或 Arrays.deepEquals,不能把普通数组对象的 equals 当作元素比较。
equals 与 hashCode 必须一致
equals 要满足自反、对称、传递、一致,并且与 null 比较为 false。相等的对象必须有相同 hashCode,反过来相同哈希值不保证相等。Java 8 Object 契约
如果只覆盖 equals,却保留基于对象身份的 hashCode,HashSet、HashMap 等基于哈希的集合可能无法按预期找到另一个相等对象。
字段多时可以使用工具,但语义仍由你决定
Apache Commons Lang 的 EqualsBuilder 可以逐字段 append 并调用 isEquals;IDE 也能生成 equals/hashCode。它们减少样板代码,却不能替你决定哪些字段代表身份。使用工具时,hashCode 也要采用一致的字段规则。
父子类比较需要格外小心:getClass 与 instanceof 不是随意互换的优化,必须符合类型设计和对称性要求。上面的 final 类刻意避开了继承扩展。
对象进入哈希集合后,不要修改参与相等和哈希计算的字段。若需要做“字段是否变化”的临时比较,可以写独立比较方法,不一定要改变整个类的 equals 语义。