开发者

Implementation of equals(): compare against implemented interface or implementing class?

开发者 https://www.devze.com 2023-03-27 13:45 出处:网络
I\'ver been wondering how to best implement equals() for a family of classes that all implement the same interface (and the client is supposed to work only with said interface and never to know about

I'ver been wondering how to best implement equals() for a family of classes that all implement the same interface (and the client is supposed to work only with said interface and never to know about implementing classes).

I haven't cooked up my own concrete example, but there are two examples in the JDK - java.lang.Number and java.lang.CharSequence that illustrate the decision:

boolean b1 = new Byte(0).equals( new Integer(0) ) );

or with CharSequ开发者_StackOverflow中文版ence

boolean b2 = "".equals(new StringBuilder());

Would you ideally want those to evaluate to true or not? Both types do implement the same datatype interface, and as a client working with Numbers (resp. CharSequences) instances I would have an easier life if equals would compare the interface types instead of the implementing types.

Now this is not an ideal example, as the JDK exposes the implementing types to the public, but suppose we had not have to uphold compatibility with what is already there - from a designers point of view: Should equals check against the interface or is it better the way it is, checking against the implementation?


Note: I understand that checking for equality against an interface can be very hard to actually implement properly in practice and its made even more tricky since equal interfaces also need to return the same hashCode(). But those are only obstacles in implementation, take for example CharSequence, although the interface is pretty small, everything required for equality checks is present whithout revealing the internal structure of the implementation (so it is principally possible to implement properly, even without knowing about future implementations in advance). But I am more interested in the design aspect, not on how to actually implement it. I wouldn't decide solely based on how hard something is to implement.


Define an abstract class that implements your interface and defines final equals()/hashCode() methods and have your customers extend that instead:

public interface Somethingable {
    public void something();
}

public abstract class AbstractSomethingable implements Somethingable {
    public final boolean equals(Object obj) {
        // your consistent implementation
    }

    public final int hashCode() {
        // your consistent implementation
    }
}

Notice that by making your class abstract, you can implements the interface without defining the interface's methods.

Your customers still have to implement the something() method, but all their instances will use your code for equals()/hashCode() (because you've made those methods final).

The difference to your customers is:

  • Using the extends keyword instead of the implements keyword (minor)
  • Not being able to extend some other class of their choosing to use your API (could be minor, could be major - if it's acceptable then go for it)


I would normally assume that "similar" objects would not be equal - for example I wouldn't expect the Integer(1) would pass equals(Long(1)) . I can imagine situations where that would be reasonable, but as the jdk needs to be a general-purpose API you wouldn't be able to make the assumption that that would always be the correct thing to do.

If you've got some sort of custom objects where it's reasonable, I think it's perfectly fine to implement an expanded definition of equals if you

  • are sure that you don't have some edge cases where you really do need the more specific equality (i.e. that would require the identical classes)
  • document it very clearly
  • make sure that hashcode behaves consistently with your new equals.


For what it's worth, I'd probably do an implementation-specific equals implementation (side note - don't forget to implement hashCode...). Interface-level equals() puts a pretty heavy burden on implementers of the interface - who might or might not be aware of the special requirement.

Often, implementation-level works fine as your client only deals with one implementation (i.e. MyNumberProcessor can works on any Number, but practically one instance of it would only have to handle Long and maybe another only Double). Generics are a great way of making sure that happens.

In the rare case where it does matter, I would probably design the client to allow injection of a Comparator or - when not available - encapsulate my Numbers into a VarTypeNumber.


I'd try to add another equals Method to my interface. How about that:

assertFalse(new Integer(0).equals(new Byte(0))); // pass
assertTrue(new Integer(0).valueEquals(new Byte(0))); // hypothetical pass

This does not produce unexpected behaviour (different types equal) but keeps the possibility open to check for equal values.

There's a somewhat related topic in effective java where equals with instanceof and getClass is discussed. Can't remember the item number, though.


I would consider any implementation of equals that returns true for two objects that do not have the same concrete type to be extremely 'surprising' behavior. If you're operating inside a box where you know at compile time every possible implementor of the interface, you can fabricate equals that make sense with only interface methods, but that's not a reality for API/framework code.

You can't even be sure that nobody's going to write an implementation of the interface that mutates its internal state when you call the methods that you used to implement equals! Talk about confusing, an equals check that returns true and invalidates itself in the process?

--

This is what I understood to be the question as far as 'checking equality against the interface':

public interface Car {

  int speedKMH();
  String directionCardinal();

}

public class BoringCorrolla implements Car {

  private int speed;
  private String directionCardinal;

  public int speedKMH() { return speed; }
  public String directionCardinal() { return directionCardinal; }

  @Override
  public boolean equals(Object obj) {
    if (obj isntanceof Car) {
      Car other = (Car) obj;
      return (other.speedKMH() == speedKMH() && other.directionCardinal().equals(directionCardinal());
    }
  }
}

public class CRAZYTAXI implements Car, RandomCar {

      public int speedKMH() { return randomSpeed(); }
      public String directionCardinal() { return randomDirection();}
    }


It is possible to define equality among different classes.

In your case, the exact equality algorithm must be specified by the interface, so any class implementing the interface must abide by it. Better yet, since the algorithm depends only on information exposed by the inferface, just implement it already, so subclasses can simply borrow it.

interface Foo

class Util 

    static int hashCode(Foo foo){ ... }

    static boolean equal(Foo a, Foo b){ ... }

    static boolean equal(Foo a, Object b)
        return (b instanceof Foo) && equal(a, (Foo)b);

class FooX implements Foo

    int hashCode()
        return Util.hashCode(this); 

    boolean equals(Object that)
        return Util.equal(this, that);
0

精彩评论

暂无评论...
验证码 换一张
取 消