如何覆盖 Java 枚举中的(最终)equals 方法?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/3564139/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-30 02:25:58  来源:igfitidea点击:

How to override the (final) equals method in java enums?

javaenumsoverridingequalsfinal

提问by neXus

I have a problem with overriding the equals method in an Enum to make it compatible with other classes. The Enum implements an interface and the idea is that all implementations of this interface can be tested for equality, regardless of their type. For Example:

我在重写 Enum 中的 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
}

If both a BasicGroupand an OtherGrouphave the same coordinates (in arbitrary order) then the equals method should return true.

如果 aBasicGroup和 anOtherGroup具有相同的坐标(以任意顺序),那么 equals 方法应该返回 true。

No problem when performing myOtherGroup.equals(BasicGroup.a)but since the equals method in Enums is final, I can't override them.

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

Is there some way to work around this? Like when testing on another BasicGroup the default equals method (reference equality)is used and when testing other classes my own implementation is used. And how do I make sure that java doesn't use the wrong one when I do BasicGroup.a.equals(myOtherGroup)?

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

回答by polygenelubricants

You can NOT@Overridea finalmethod (§8.4.3.3); this much is clear. enumtypes (§8.9) are treated very specially in Java, which is why the equalsis final(also clone, hashCode, etc.) It's simply not possible to @Overridethe equalsmethod of an enum, nor would you really want to in a more typical usage scenario.

不能@Override使用final方法(第8.4.3.3 节);这很清楚。enum类型(第8.9 节)在 Java 中被非常特殊地对待,这就是为什么equalsis final(还有clonehashCode等)在 an@Overrideequals方法中根本不可能enum,在更典型的使用场景中您也不会真的想要。

HOWEVER, looking at the big picture, it looks like you are trying to follow the pattern recommended in Effective Java 2nd Edition, Item 34: Emulate extensible enums with interfaces(see the language guidefor more information about enum):

然而,从大局来看,您似乎正在尝试遵循Effective Java 2nd Edition, Item 34: Emulate extensible enums with interfaces 中推荐的模式(有关更多信息,请参阅语言指南enum):

You have defined this interface(now documented explicitly for expected equalsbehavior):

你已经定义了这个interface(现在明确记录了预期的equals行为):

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);
}

It is perfectly acceptable for an interfaceto define how equalsmethod for implementors should behave, of course. This is exactly the case with, e.g. List.equals. An empty LinkedListis equalsto an empty ArrayListand vice versa, because that's what the interfacemandates.

当然interface,定义equals实现者的方法应该如何表现是完全可以接受的。例如,情况正是如此List.equals。空LinkedListequals一个空ArrayList,反之亦然,因为那是什么interface任务。

In your case, you've chosen to implement some Groupas enum. Unfortunately you now can't implement equalsas per the specification, since it's finaland you can't @Overrideit. However, since the objective is to comply to the Grouptype, you can use decorator patternby having a ForwardingGroupas follows:

在您的情况下,您选择将一些Group作为enum. 不幸的是,您现在无法equals按照规范实施,因为它是final,您也不能@Override。但是,由于目标是符合Grouptype,您可以使用装饰器模式ForwardingGroup如下所示:

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!
   }
}

Now, instead of using your enumconstants directly as Group, you wrapthem in an instance of a ForwardingGroup. Now this Groupobject will have the desired equalsbehavior, as specified by the interface.

现在,不是enum直接使用常量 as Group,而是它们包装在 a 的实例中ForwardingGroup。现在,此Group对象将具有所需的equals行为,如interface.

That is, instead of:

也就是说,而不是:

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

You now have something like:

你现在有类似的东西:

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


Additional notes

补充笔记

The fact that enum BasicGroups implements Group, even though it does not itself follow the specification of Group.equals, should be very clearly documented. Users must be warned that constants must be e.g. wrapped inside a ForwardingGroupfor proper equalsbehavior.

事实上enum BasicGroups implements Group,即使它本身不遵循规范Group.equals,也应该非常清楚地记录在案。必须警告用户,常量必须例如包装在 a 中ForwardingGroup才能获得正确的equals行为。

Note also that you can cache instances of ForwardingGroup, one for each enumconstants. This will help reduce the number of objects created. As per Effective Java 2nd Edition, Item 1: Consider static factory methods instead of constructors, you may consider having ForwardingGroupdefine a static getInstance(Group g)method instead of a constructor, allowing it to return cached instances.

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

I'm assuming that Groupis an immutable type (Effective Java 2nd Edition, Item 15: Minimize mutability), or else you probably shouldn't implement it with enumin the first place. Given that, consider Effective Java 2nd Edition, Item 25: Prefer lists to arrays. You may choose to have getCoordinates()return a List<Point>instead of Point[]. You can use Collections.unmodifiableList(another decorator!), which will make the returned Listimmutable. By contrast, since arrays are mutable, you'd be forced to perform defensive copying when returning a Point[].

我假设这Group是一个不可变类型(Effective Java 2nd Edition,Item 15: Minimize mutability),否则您可能不应该enum首先实现它。鉴于此,请考虑Effective Java 2nd Edition, Item 25: Prefer lists to arrays。您可以选择getCoordinates()返回 aList<Point>而不是Point[]。您可以使用Collections.unmodifiableList(另一个装饰器!),这将使返回的List不可变。相比之下,由于数组是可变的,因此您在返回 a 时将被迫执行防御性复制Point[]

See also

也可以看看

回答by aioobe

It's not possible to do this in Java. (The sole purpose of the final keyword when it comes to methods, is to prevent overriding!)

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

equalsand a few other methods on Enums are final, so you can't change the behavior of them. (And you shouldn't:) Here is my answer to a related question:

equalsEnums 上的其他一些方法是最终的,所以你不能改变它们的行为。(你不应该:)这是我对一个相关问题的回答



The intuition of clients that deal with enum constants is that two constants are equalif and only if they are the same constant. Thus any other implementation than return this == otherwould be counterintuitive and error prone.

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

Same reasoning applies to hashCode(), clone(), compareTo(Object), name(), ordinal(), and getDeclaringClass().

同样的道理也适用于hashCode()clone()compareTo(Object)name()ordinal(),和getDeclaringClass()

The JLS does not motivate the choice of making it final, but mentions equals in the context of enums here. Snippet:

JLS 不会促使选择使其成为最终版本,但在此处的枚举上下文中提到了 equals 。片段:

The equals method in Enum is a final method that merely invokes super.equals on its argument and returns the result, thus performing an identity comparison.

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

回答by Ricky Clarkson

You can solve this by calling your method hasSameCoordinatesAs, or similar, rather than equals.

您可以通过调用您的方法 hasSameCoordinatesAs 或类似方法而不是 equals 来解决此问题。

equals for enums is defined in the language specification, so you can't hope to redefine it.

枚举的 equals 是在语言规范中定义的,所以你不能指望重新定义它。

回答by irreputable

Equality is quite elusive. Different contexts require different equality relations. By having equals() method on Object, Java imposes an "intrinsic" equality, and APIs, like Set, depend on it.

平等是相当难以捉摸的。不同的上下文需要不同的平等关系。通过在 Object 上使用 equals() 方法,Java 强加了“内在”相等性,而像 Set 这样的 API 依赖于它。

Meanwhile, ordering isn't considered "intrinsic", two objects can be ordered differently in different contexts, and APIs usually allow us to supply a comprator, i.e., a custom ordering relation.

同时,排序不被认为是“内在的”,两个对象可以在不同的上下文中以不同的方式排序,API 通常允许我们提供一个比较器,即自定义排序关系。

This is interesting. In math terms, equality, like order, is just a relation, and there can be different equality relations. The concept of "intrinsic equality" isn't holy.

这很有趣。在数学上,平等和秩序一样,只是一种关系,可以有不同的平等关系。“内在平等”的概念并不神圣。

so let's have an Equal-ator too, and change APIs to accept custom equality relations:

因此,让我们也有一个 Equal-ator,并更改 API 以接受自定义相等关系:

interface Equalator
    boolean equal(a, b)

public HashSet( Equalator equalator )

Actually, we can build wrappers around current collection APIs, and add this feature of new equality.

实际上,我们可以围绕当前集合 API 构建包装器,并添加这个新的相等特性。

This might answer your question. Why do you have a dependency on equals() in the first place? And can you remove that, and depend instead on "equalator"? Then you are set.

这可能会回答您的问题。为什么你首先依赖于 equals() ?你能去掉它,而是依赖于“均衡器”吗?然后你就设置好了。