0

If a class implements multiple interfaces, to which interface should equals() be implemented to? And if you are writing to an interface, how do you know whether the classes implementing the interface have implemented equals() to the interface you are using?

For example, lets say I have a Collection of Coloured objects. These object could be Dogs, Marbles or Chairs, which have their own equals() implementation for their own class hierarchy.

I would like to check that two Coloured objects are equal, which from my perspective means they have the same colour. However this will not work because the equals() method is implemented for a different class hierarchy. The same problems apply to sorting, comparing and checking for containment.

    Coloured obj1 = new ChocolateLabrador();
    Coloured obj2 = new OakChair();
    obj1.getColour().equals(obj2.getColour()); // returns true
    obj1.equals(obj2); // returns false

To benefit from polymorphism I should not have to be concerned with the implementation of the Coloured objects. Is there a way to program the classes so that equals() can work from the two different perspectives? Or is there a standard way to document or annotate an interface to show that equals() will not work from that viewpoint?

Gregory
  • 1
  • 4
  • Equals pertains to the concrete class, regardless of interfaces. Interfaces are about defining what an instance does, not how. Read Joshua Bloch's "Effective Java" to learn how to think about and implement equals and hashCode correctly. – duffymo Feb 07 '16 at 03:31
  • Possible duplicate of [Method name collision in interface implementation - Java](http://stackoverflow.com/questions/2598009/method-name-collision-in-interface-implementation-java) – OneCricketeer Feb 07 '16 at 03:34
  • Have done little work in Java, but thinking In C#, if I implement the interface, that's the one `equals()` of the class *from* the interface, but you can implement your own private or public methods called `equals()` which takes the different classes you've defined. In the implemented version, simply cast each object as the respective type, and pass it to the correct method to check for equality. Each class will have to define the relationship to another class of course, else I imagine the default object equality would be implied. – Tyler StandishMan Feb 07 '16 at 03:37
  • @duffymo I have read Bloch's _Effective Java_. Equals does not pertain to a single concrete class, it pertains to concrete classes implementing a common interface. If you read Chapter 3 pp. 38-40 he says "This has the effect of equating objects only if they have the same implementation class. While this may not seem so bad, the consequences are unacceptable." He then talks about how it must be possible for CounterPoint and Point to be equal because they have the same specification, and therefore the `equals()` implementation should use `instanceof` not `getClass()`. – Gregory Feb 07 '16 at 04:37
  • @cricket_007 This is not a duplication of method name collision in interfaces because this is dealing with `equals()` which has a fixed specification in `Object` and has particular restrictions such as reflexivity, symmetry and transitivity which makes it a more complex issue. – Gregory Feb 07 '16 at 04:40
  • Sure, but equals can't be defined in multiple interfaces, which seems to me to be some subset of your question – OneCricketeer Feb 07 '16 at 04:41
  • @Tyler If I understand you correctly, you suggest overloading the `equals()` method for each class, in this case `Coloured`, `Labrador` and `Chair`. I think this would be problematic because `ChocolateLabrador` implements both `Labrador` and `Coloured` so if you passed this object to `equals()` you would still need to pick one interface to take precedence for the implementation. The method does not know the type of the variable being called. You may also have issues from `equals()` contractually needing to be symmetric. – Gregory Feb 07 '16 at 04:50
  • @cricket_007 Agreed, however that question is resolved to my satisfaction, and I understand that the same method cannot be implemented in two different ways in Java. My question is more subtle than that and is about reconciling multiple drivers. (1) Different concrete classes implementing a common and complete interface can be equal. (2) Variables should be written to interface. (3) Classes can extend multiple interfaces. So if you have a variable set to some interface, how do you know if `equals()` will work how you expect? The solution may be outside of the how `equals()` is implemented. – Gregory Feb 07 '16 at 05:09
  • I can see three solutions at the moment: - Scrap drivers 1 and 2 mentioned above and have equals only applicable to concrete classes and write variables only to the concrete classes. This will break polymorphism and it is not recommended by Bloch in _Effective Java_. - Scrap 3 and classes cannot extend multiple interfaces. This will limit polymorphism. - Add special JavaDoc or annotations to interfaces such as `Coloured` to explain that these types have `equals()` and `hashCode()` etc. implemented for another perspective. – Gregory Feb 07 '16 at 05:19

4 Answers4

2

to which interface should equals() be implemented to?

There is only one equals which matters, this is

 public boolean equals(Object other)

There is no interface here.

how do you know whether the classes implementing the interface have implemented equals() to the interface you are using?

You have to say which class are equals, if in doubt, only the same class is equal, no others.

I would like to check that two Coloured objects are equal,

So a Red Dog is equal to and the same as a Red Chair? Does that make sense?

If you are sorting, it is common to sort based on a field of a class. e.g.

List<Coloured> colours = ...
colours.sorted(Comparator.comparing(Coloured::getColour));

This doesn't mean that two things with the same colour are equal, only that there is no way to decide how they should be sorted. You could later decide to sort them by size or age and for that sort the size or age is the right field to use, but two things the same age are not equal.

Peter Lawrey
  • 525,659
  • 79
  • 751
  • 1,130
  • Firstly, `equals()` needs to be implemented so that different concrete classes which implement a common interface can be equal. This is what I mean by "which interface should `equals()` be implemented to?" when the concrete class implements more than one interface. Secondly, if you have a group of `Coloured` objects you do not know whether they are `RedDog` or `RedChair` classes, all you know is that they implement `Coloured`. The question is how do you know whether `equals()` will work as expected for `Coloured` or any other interface type? I believe I have now solved this problem. – Gregory Feb 07 '16 at 16:14
  • @Gregory equals should only apply when two objects are the same in all contexts. If you want to sort objects in a custom way considering only a sub-set of fields, use a custom comparator. – Peter Lawrey Feb 07 '16 at 17:20
0

equals needs to be identical anyways, no matter in which interface it was explicitly defined. It's also in Object anyway.

equals compares objects, and a black dog is not equal to a black cat, eventhough they both implement IColor interface.

class Dog /* extends or implements are not important */ {
    boolean equals(Object other) {
        if (!other instanceof Dog) return false;
        // ...
    }

BTW also be reminded that hashCode() and equals() are expected to behave similarly.

Gavriel
  • 18,880
  • 12
  • 68
  • 105
  • The problem is that if you have a bunch of `Coloured` object you do not know whether they are cats, marbles, chairs, goats or the abstract concept of a colour. All you know is that they implement `Coloured`, so you can only assume they implement `equals()` based on `Coloured`'s fields and that when you use `equals()` you are checking whether they have the same colour. – Gregory Feb 07 '16 at 05:27
  • No, I repeat: equals doesn't belong to any of the Interfaces. At least not it's implementation. It compares `this`, which therefore has a known (or at least assumed) class, to an `other` Object. Usually the 1st line of equals checks whether `object instanceof Class`, and if not it returns false – Gavriel Feb 07 '16 at 06:32
  • In this context, `this` will have multiple interfaces that it implements. The `other` argument will have a given type, but the other interfaces it implements are unknown and the concrete class is unknown. The question is which interface of `this` should be used to compare against `other`. Furthermore, if you have a group of objects of a specific type, how do you know if `equals()` will work with them? I believe I have found the answer to these questions now. – Gregory Feb 07 '16 at 15:47
0

You shouldn't implement equals to any of the interfaces of the class. Instead, you should be implementing it to deal with the exact equality of objects.

If you would like interface-specific equality comparison, you should define and implement an interface for comparing for interface equality, i.e.

interface ColouredComparer {
    boolean compareEqual(Coloured a, Coloured b);
}
interface CollectionComparer {
    boolean compareEqual(Collection a, Collection b);
}

These interfaces should be implemented outside the scope of your classes, so that the equality of the class would remain tied to how it is defined within java.lang.Object.

If you would rather implement the comparison inside the class, have your classes implement different interfaces, one for an interface in terms of which you would like to compare equality:

interface ColouredComparable {
    boolean equalTo(Coloured other);
}
interface CollectionComparable {
    boolean equalTo(Collection other);
}
Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
  • The recommendation in Bloch's _Effective Java_ is that you should implement `equals()` to a common interface for a set of concrete classes. In Chapter 3 pp. 38-40 he says "This has the effect of equating objects only if they have the same implementation class. While this may not seem so bad, the consequences are unacceptable." He then talks about how it must be possible for `CounterPoint` and `Point` to be equal because they have the same specification, and therefore the `equals()` implementation should use `instanceof` not `getClass()`. – Gregory Feb 07 '16 at 05:35
  • @Gregory The "unacceptable consequences" are a direct result of a shortcoming in the design of Java language and standard library - specifically, its lack of `Equitable` interface, an equivalent of `Comparable` for equality, and inability to implement generic interface multiple times for different type argument. Joshua's solution provides a crutch that lets you work around this limitation for a single line of inheritance, but it has its limitations, too. Your question exposes one such limitation (i.e. multiple lines of inheritance). My answer provides a solution to it. – Sergey Kalinichenko Feb 07 '16 at 10:15
  • Your answer provides an implementation solution but it does not solve the problem. If I have a group of objects of a certain type, how do I know that `equals()` will not work and that I need to use another method? So in this case, how do I know `equals()` will not work with `Coloured` and that I need to go find and use `ColouredComparable`? I believe I have now solved this problem. – Gregory Feb 07 '16 at 15:58
-1

I have had a revelation which I think is the best answer to this question.

There are actually two different semantic types of interface. Interfaces can either be nouns or adjectives and verbs. A concrete class should implement only one noun interface and can implement as many adjectives and verbs interfaces as it likes.

The noun interface forms the class hierarchy and is where the equals() method should be implemented. Concrete classes which share a common and complete noun interface can be equal and equals() should be implemented to support this.

The equals() method should not equate objects which implement a single adjective and verb interface. As others have suggested, this is better done using a separate method or class. I think the best way of doing this is a separate question.

I think a key point is that if you have a collection of types, you know if it implements equals() against that type by looking at whether the interface name is a noun, adjective or verb. equals() works as expected for noun types but not for adjective or verb types.


Noun interface
Examples: Object, Dog, Chair
The equals() method of a concrete class is implemented against the single noun interface it implements in its class hierarchy which is common and complete. By common and complete I mean that all the concrete classes implementing the interface implement a common interface, and that this interface includes all the value components of these concrete classes.

Adjective or Verb interface
Examples: Iterable, Comparable, Coloured
equals() method is not implemented against these interfaces. A separate method should be used to used for equality, sorting etc. the value components of these types.


Here is a coded example:

// Adjective interface
// Classes can implement as many of these as they like
interface Coloured {
    String getColour();

    // These are examples of how you could implement alternatives to using equals()
    default boolean sameColour(Coloured c) {
        return sameColour(this, c);
    }
    static boolean sameColour(Coloured a, Coloured b) {
        if (a.getColour().equals(b.getColour()) {
            return true;
        } else {
            return false;
        }
    }
    static List<Coloured> sort(List<Coloured> list) {
        // …
    }
    static boolean compare(List<Coloured> a, List<Coloured> b) {
        // …
    }
}

// Noun interface
// Classes should only implement one of these at most
interface Dog extends Coloured {
    String woof();
}

abstract class AbstractDog implements Dog {
    public boolean equals(Object o) {
        if (o == null) {
            return false;
        }
        if (!(o instanceof Dog)) {
            return false;
        }
        final Dog d = (Dog) o;
        return d.woof().equals(this.woof()) &&
            d.getColour().equals(this.getColour());
    }
}

class GoldenRetriever extends AbstractDog {
    public String woof() {
        return “RuffRuff”;
    }
    public String getColour() {
        return “Gold”;
    }
}

class ChocolateLabrador extends AbstractDog {
    public String woof() {
        return “WoofLick”;
    }
    public String getColour() {
        return “Chocolate”;
    }
}

class GoldRing implements Coloured {
    public String getColour() {
        return “Gold”;
    }
}

final Coloured obj1 = new GoldenRetriever();
final Coloured obj2 = new GoldRing();
// I know these are Coloured objects but I do not know their implementation
// But because Coloured is an adjective I know not to use equals()
final boolean isSameColour = obj1.sameColour(obj2); // Returns true for same colour but different types

final Dog dog1 = new GoldenRetriever();
final Dog dog2 = new ChocolateLabrador();
// I know these are Dog objects but I do not know their implementation
// But because Dog is a noun I know I can use equals()
// The objects have different implementations but have the same common complete interface
final boolean isEqualDog = dog1.equals(dog2);
final boolean isDogSameColour = Coloured.sameColour(dog1, dog2);

Thanks to everyone who responded.

Gregory
  • 1
  • 4
  • Unfortunately you haven't understand the most important parts what every other answer told you with different words. Your example of Dog vs AbstractDog IMHO shows that you still need to read about the topic. I'll leave you with this question as homework: is a small ChocolateLabrador cub equal to it's big father just because they bark the same way? – Gavriel Feb 07 '16 at 16:16
  • @Gavriel I think I understand the topic very well. If you want young and old dogs you would change `Dog` interface to have a `getAge()` method. Then you would change the implementation of `AbstractDog` to implement `getAge()` and alter `equals()` to also check that the ages of the two objects are the same. But this has nothing to do with the question that I asked. – Gregory Feb 07 '16 at 17:59