简体   繁体   中英

If a class implements multiple interfaces, to which interface should `equals()` be implemented to?

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 Dog s, Marble s or Chair s, 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?

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. eg

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.

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.

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, ie

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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