Java中枚举与非枚举对象equals比较的最佳实现方式
Java 枚举类型的equals方法无法被重写,因此在FirstTwo为枚举类型的前提下,不存在任何合规方案可以让ONE.equals(new AnotherOne())返回true。
所有枚举类都会隐式继承java.lang.Enum,该类中定义的equals方法使用final修饰,所有枚举子类都无权覆盖其实现。Enum原生的equals逻辑仅判断两个引用是否指向同一个枚举常量实例,完全基于对象身份判定,和枚举实现的接口、接口方法返回值没有关联。
你可以根据业务场景选择以下两种合规方案实现基于value()返回值的跨类型相等判定:
方案1:在公共接口中自定义值相等方法
不依赖Object类原生的equals,在AnInt接口中增加默认的值相等判定方法,所有实现类自动具备该能力,完全保留FirstTwo的枚举特性:
public interface AnInt { int value(); // 基于value值的相等判定 default boolean isSameValue(AnInt target) { return target != null && this.value() == target.value(); } } // 补全枚举的构造逻辑即可正常使用 public enum FirstTwo implements AnInt { ONE(() -> 1), TWO(() -> 2); private final IntSupplier valueSupplier; FirstTwo(IntSupplier valueSupplier) { this.valueSupplier = valueSupplier; } @Override public int value() { return valueSupplier.getAsInt(); } }
使用时调用自定义方法即可得到预期结果:
// 返回true ONE.isSameValue(new AnotherOne());
该方案不会破坏枚举的原生特性,依然可以正常使用switch匹配、EnumSet、EnumMap等枚举专属能力,是日常开发的首选方案。
方案2:将枚举改为普通常量类,自由重写equals
如果你必须要使用标准equals方法完成跨类型相等判定,就不要用枚举类型定义常量,改为构造私有、实例静态常量化的普通final类,即可自由重写equals和hashCode逻辑:
import java.util.function.IntSupplier; public final class FirstTwo implements AnInt { public static final FirstTwo ONE = new FirstTwo(() -> 1); public static final FirstTwo TWO = new FirstTwo(() -> 2); private final IntSupplier valueSupplier; private FirstTwo(IntSupplier valueSupplier) { this.valueSupplier = valueSupplier; } @Override public int value() { return valueSupplier.getAsInt(); } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof AnInt)) return false; return this.value() == ((AnInt) o).value(); } @Override public int hashCode() { return Integer.hashCode(value()); } }
此时调用FirstTwo.ONE.equals(new AnotherOne())就会返回true,但要注意该方案下FirstTwo不再是枚举类型,无法使用switch匹配、EnumSet/EnumMap等枚举专属能力,需要提前评估业务影响。
不要尝试用反射、字节码修改等黑科技绕过枚举equals的final限制:枚举的身份相等是Java语言和JDK很多基础逻辑的默认约定,强行修改会导致集合操作、序列化、枚举工具类等场景出现难以排查的诡异bug,生产环境绝对禁止这类操作。
内容的提问来源于stack exchange,提问作者Max Petrov

