简体   繁体   English

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

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

I have a problem with overriding the equals method in an Enum to make it compatible with other classes. 我在覆盖Enum中的equals方法时遇到问题,使其与其他类兼容。 The Enum implements an interface and the idea is that all implementations of this interface can be tested for equality, regardless of their type. Enum实现了一个接口,其思路是可以测试此接口的所有实现是否相等,无论其类型如何。 For Example: 例如:

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 BasicGroup and an OtherGroup have the same coordinates (in arbitrary order) then the equals method should return true. 如果BasicGroupOtherGroup具有相同的坐标(按任意顺序),则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方法是final,我无法覆盖它们。

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. 就像在另一个BasicGroup上测试时一样,使用默认的equals方法(引用相等) ,并且在测试其他类时使用我自己的实现。 And how do I make sure that java doesn't use the wrong one when I do BasicGroup.a.equals(myOtherGroup) ? 当我做BasicGroup.a.equals(myOtherGroup)时,如何确保java不使用错误的?

You can NOT @Override a final method ( §8.4.3.3 ); 不能 @Override一个final方法( §8.4.3.3 ); this much is clear. 这很清楚。 enum types ( §8.9 ) are treated very specially in Java, which is why the equals is final (also clone , hashCode , etc.) It's simply not possible to @Override the equals method of an enum , nor would you really want to in a more typical usage scenario. enum类型(第8.9节 )在Java中被非常特殊地处理,这就是为什么equalsfinal (也就是clonehashCode等)。它根本不可能@Override enumequals方法,也不是你真的想要的更典型的使用场景。

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 guide for more information about enum ): 但是 ,从整体来看,看起来您正在尝试遵循Effective Java 2nd Edition中建议的模式,第34项:使用接口模拟可扩展枚举 (有关enum更多信息,请参阅语言指南 ):

You have defined this interface (now documented explicitly for expected equals behavior): 您已定义此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 interface to define how equals method for implementors should behave, of course. 当然, interface定义实现者的equals方法应该如何表现是完全可以接受的。 This is exactly the case with, eg List.equals . 这正是例如List.equals的情况。 An empty LinkedList is equals to an empty ArrayList and vice versa, because that's what the interface mandates. LinkedList equalsArrayList ,反之亦然,因为这是interface要求的。

In your case, you've chosen to implement some Group as enum . 在您的情况下,您已选择将某个Group实现为enum Unfortunately you now can't implement equals as per the specification, since it's final and you can't @Override it. 不幸的是,你现在不能按照规范实现equals ,因为它是final ,你不能@Override它。 However, since the objective is to comply to the Group type , you can use decorator pattern by having a ForwardingGroup as follows: 但是,由于目标是遵守Group 类型 ,因此可以通过具有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 enum constants directly as Group , you wrap them in an instance of a ForwardingGroup . 现在,不是将enum常量直接用作Group ,而是它们包装ForwardingGroup的实例中。 Now this Group object will have the desired equals behavior, as specified by the interface . 现在,此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 . enum BasicGroups implements Group的事实,即使它本身不遵循Group.equals的规范,也应该非常清楚地记录 Users must be warned that constants must be eg wrapped inside a ForwardingGroup for proper equals behavior. 必须警告用户必须将常量包装在ForwardingGroup以获得正确的equals行为。

Note also that you can cache instances of ForwardingGroup , one for each enum constants. 另请注意,您可以缓存ForwardingGroup实例,每个enum常量一个。 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 ForwardingGroup define a static getInstance(Group g) method instead of a constructor, allowing it to return cached instances. 根据Effective Java 2nd Edition,第1项:考虑静态工厂方法而不是构造函数 ,您可以考虑让ForwardingGroup定义static getInstance(Group g)方法而不是构造函数,允许它返回缓存实例。

I'm assuming that Group is an immutable type ( Effective Java 2nd Edition, Item 15: Minimize mutability ), or else you probably shouldn't implement it with enum in the first place. 我假设Group是一个不可变类型( Effective Java 2nd Edition,Item 15:Minimize mutability ),否则你可能不应该首先使用enum实现它。 Given that, consider Effective Java 2nd Edition, Item 25: Prefer lists to arrays . 鉴于此,请考虑Effective Java 2nd Edition,Item 25:Prefer list to arrays You may choose to have getCoordinates() return a List<Point> instead of Point[] . 您可以选择让getCoordinates()返回List<Point>而不是Point[] You can use Collections.unmodifiableList (another decorator!), which will make the returned List immutable. 您可以使用Collections.unmodifiableList (另一个装饰器!),这将使返回的List不可变。 By contrast, since arrays are mutable, you'd be forced to perform defensive copying when returning a Point[] . 相比之下,由于数组是可变的,因此在返回Point[]时,您将被迫执行防御性复制。

See also 也可以看看

It's not possible to do this in Java. 在Java中不可能这样做。 (The sole purpose of the final keyword when it comes to methods, is to prevent overriding!) (当涉及到方法时,final关键字的唯一目的是防止覆盖!)

equals and a few other methods on Enums are final, so you can't change the behavior of them. Enums上的equals和一些其他方法是最终的,因此您无法更改它们的行为。 (And you shouldn't :) Here is my answer to a related question : (你不应该 :)这是我对相关问题的回答


The intuition of clients that deal with enum constants is that two constants are equal if and only if they are the same constant. 处理枚举常量的客户端的直觉是,当且仅当它们是相同的常量时,两个常量是equal Thus any other implementation than return this == other would be counterintuitive and error prone. 因此,除了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 . JLS并没有激励选择让它成为最终版本,但在这里的枚举语中提到了相同的内容。 Snippet: 片段:

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并返回结果,从而执行身份比较。

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. enums的equals在语言规范中定义,因此您无法重新定义它。

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, ie, a custom ordering relation. 同时,排序不被视为“内在的”,两个对象可以在不同的上下文中以不同的方式排序,并且API通常允许我们提供comprator,即自定义排序关系。

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? 为什么你首先依赖于equals()? And can you remove that, and depend instead on "equalator"? 你可以删除它,并依赖“equalator”吗? Then you are set. 然后你就定了。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM