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:
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.
No problem when performing myOtherGroup.equals(BasicGroup.a)
but since the equals method in Enums is final, I can't override them.
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)
?
You can NOT @Override
a final
method (§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.
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
):
You have defined this interface
(now documented explicitly for expected equals
behavior):
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. This is exactly the case with, e.g. List.equals
. An empty LinkedList
is equals
to an empty ArrayList
and vice versa, because that's what the interface
mandates.
In your case, you've chosen to implement some Group
as enum
. Unfortunately you now can't implement equals
as per the specification, since it's final
and you can't @Override
it. However, since the objective is to comply to the Group
type, you can use decorator pattern by having a ForwardingGroup
as follows:
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
. Now this Group
object will have the desired equals
behavior, as specified by the 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);
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 ForwardingGroup
for proper equals
behavior.
Note also that you can cache instances of ForwardingGroup
, one for each enum
constants. 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.
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. 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 List
immutable. By contrast, since arrays are mutable, you'd be forced to perform defensive copying when returning a Point[]
.
com.google.common.collect.ForwardingObject
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!)
equals
and 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:
The intuition of clients that deal with enum constants is that two constants are equal
if and only if they are the same constant. Thus any other implementation than return this == other
would be counterintuitive and error prone.
Same reasoning applies to hashCode()
, clone()
, compareTo(Object)
, name()
, ordinal()
, and getDeclaringClass()
.
The JLS does not motivate the choice of making it final, but mentions equals in the context of enums here. 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With