views:

147

answers:

4

Hi,

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)?

+6  A: 

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.

aioobe
+2  A: 

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

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

Ricky Clarkson
+5  A: 

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

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 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[].

See also

polygenelubricants
I've revisited this problem. A few things to add: Effective Java 2nd Edition, Item 15: Always override `hashCode` when you override `equals`. `Group` should specify what its `hashCode` should return (just like `List.hashCode`). Another idea: use an adapter pattern instead of decorator. Essentially your `enum` itself does not `implements Group` (since it can't really do that without violating the `equals/hashCode` contract). So instead, each constant holds a reference to a `Group` wrapper which does the `equals/hashCode` for it.
polygenelubricants
That is, instead of `Group g = BasicGroup.A;` (which you now can't do since `BasicGroup` no longer `implements Group`, you do `Group g = BasicCoordinates.A.asGroup()`, where the method returns a reference to a `final Group wrapper` field. This way you don't have to worry about managing cached instances of decorators, since each constant holds its own wrapper. I can elaborate on this concept in greater detail if necessary.
polygenelubricants
This adapter pattern is an even better solution. Transparent and with less clutter. I already used a variant of this before in another project so there's no need to elaborate. Thanks!
neXus
@neXus: well since you agree that it's a better solution, I will revise the answer to use that technique (since I also think it's better) without fear of losing the acceptance =) It will be beneficial to others as well.
polygenelubricants
Sure, no need to worry. I like your style and the answer will remain correct yet improved. It would indeed be nice for others too.
neXus
+2  A: 

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.

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.

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:

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.

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.

irreputable
Very nice solution! I don't realy _need_ equals, I just supposed equals was the best thing for doing the job.
neXus
Guava already defines an interface for this use case: http://code.google.com/p/guava-libraries/source/browse/trunk/src/com/google/common/base/Equivalence.java
Willi