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.