如何在java枚举中覆盖(final)equals方法?

2022-09-03 12:12:38

我在枚举中重写 equals 方法以使其与其他类兼容时遇到问题。Enum实现了一个接口,其思想是可以测试该接口的所有实现是否相等,无论其类型如何。例如:

public interface Group {
    public Point[] getCoordinates();
}

public enum BasicGroups implements Group {
    a,b,c; // simplified, they actually have constructors
    // + fields and methods
}

public class OtherGroup implements Group {
    // fields and methods
}

如果 a 和 an 具有相同的坐标(按任意顺序),则 equals 方法应返回 true。BasicGroupOtherGroup

执行时没有问题,但是由于枚举中的 equals 方法是最终的,因此我无法覆盖它们。myOtherGroup.equals(BasicGroup.a)

有没有办法解决这个问题?就像在另一个 BasicGroup 上测试时一样,使用默认的 equals 方法(引用相等),在测试其他类时,使用我自己的实现。我如何确保Java在我这样做时不会使用错误的java?BasicGroup.a.equals(myOtherGroup)


答案 1

你不能种方法(§8.4.3.3);这一点很清楚。 类型 (§8.9) 在 Java 中处理得非常特殊,这就是为什么 is (also , 等)对于 一个 的方法来说,这根本不可能,在更典型的使用场景中,你也不会真正想要。@OverridefinalenumequalsfinalclonehashCode@Overrideequalsenum

但是,从大局来看,您似乎正在尝试遵循 Effective Java 2nd Edition, Item 34: Emulateable exeable enum with interface 中推荐的模式(有关以下内容的更多信息,请参阅语言指南):enum

您已经定义了以下内容(现在已针对预期行为明确记录):interfaceequals

public interface Group implements Group {
    public Point[] getCoordinates();

    /*
     * Compares the specified object with this Group for equality. Returns true
     * if and only if the specified object is also a Group with exactly the same
     * coordinates
     */
    @Override public boolean equals(Object o);
}

当然,对于 一个 定义实现者的方法应该如何表现是完全可以接受的。例如List.equals就是这种情况。空的就是空的,反之亦然,因为这是授权的。interfaceequalsLinkedListequalsArrayListinterface

在您的情况下,您已选择将某些实现为 .不幸的是,您现在无法按照规范实现,因为它是,而您不能实现它。但是,由于目标是符合类型,因此可以通过如下所示来使用装饰器模式Groupenumequalsfinal@OverrideGroupForwardingGroup

public class ForwardingGroup implements Group {
   final Group delegate;
   public ForwardingGroup(Group delegate) { this.delegate = delegate; }

   @Override public Point[] getCoordinates() {
       return delegate.getCoordinates();
   }
   @Override public boolean equals(Object o) {
       return ....; // insert your equals logic here!
   }
}

现在,不要直接将常量用作 ,而是将它们包装在 .现在,此对象将具有所需的行为,如 .enumGroupForwardingGroupGroupequalsinterface

也就是说,而不是:

// before: using enum directly, equals doesn't behave as expected
Group g = BasicGroup.A;

您现在拥有如下内容:

// after: using decorated enum constants for proper equals behavior
Group g = new ForwardingGroup(BasicGroup.A);

附加说明

事实上,即使它本身并不遵循 的规范,也应该非常清楚地记录下来。必须警告用户,例如,常量必须包装在 a 中才能正确执行。enum BasicGroups implements GroupGroup.equalsForwardingGroupequals

另请注意,您可以为每个常量缓存 一个 的实例。这将有助于减少创建的对象数。根据 Effective Java 2nd Edition 的第 1 项:考虑静态工厂方法而不是构造函数,您可以考虑定义方法而不是构造函数,以允许它返回缓存的实例。ForwardingGroupenumForwardingGroupstatic getInstance(Group g)

我假设这是一个不可变的类型(有效的Java 2nd Edition,项目15:最小化可变性),否则你可能不应该首先实现它。鉴于此,请考虑有效的Java第2版,第25项:更喜欢列表而不是数组。您可以选择返回 a 而不是 。您可以使用 Collections.unmodifiableList(另一个装饰器!),这将使返回的不可变。相比之下,由于数组是可变的,因此在返回 .GroupenumgetCoordinates()List<Point>Point[]ListPoint[]

另请参见


答案 2

在Java中不可能做到这一点。(在方法方面,最终关键字的唯一目的是防止覆盖!

equals枚举上的其他一些方法是最终的,因此您无法更改它们的行为。(你不应该:)以下是我对一个相关问题的回答:


处理枚举常量的客户端的直觉是,两个常量是当且仅当它们是相同的常量。因此,任何其他实现都是违反直觉的,并且容易出错。equalreturn this == other

相同的推理适用于 、 、 、 和 。hashCode()clone()compareTo(Object)name()ordinal()getDeclaringClass()

JLS并没有激励选择使其成为最终的,但在这里的枚举上下文中提到平等。片段:

Enum 中的 equals 方法是一个最终方法,它仅在其参数上调用 super.equals 并返回结果,从而执行标识比较。


推荐