0

There's a similar question here (Implementing equals method using compareTo) - which in the end recommends to reuse .compareTo() to implement .equals() as long as the contracts aren't violated - my focus here is on the contract x.equals(y) == y.equals(x). A simple implementation of .equals() would/could be

public boolean equals(Object obj) {
  return obj != null && (obj instanceof MyClass) && 0 == this.compareTo((MyClass)obj);
}

So here's my problem: When I have a ParentClass implementing it this way, and a ChildClass which extends ParentClass, I will have the problem that an instance of ChildClass is also an instance of ParentClass, but not vice versa.

So for an instance parent of ParentClass and an instance child of ChildClass it's at least possible - using the above .equals() - that child.equals(parent) != parent.equals(child).

So how to implement .equals() using .compareTo() and guarantee child.equals(parent) == parent.equals(child)?

Any ideas? Thanks.

edit: the origin of this question is SonarQube which complains about the simple implementation not being symmetric - the above question is my interpretation of SonarQube's complaining. The solution should be symmetric but shouldn't involve too much changes.

Community
  • 1
  • 1
outofmind
  • 1,430
  • 1
  • 20
  • 37
  • 1
    as a sidenode, the check `obj != null` will also be done by `obj instanceof MyClass`, which makes the first one redundant. – SomeJavaGuy Jul 04 '16 at 11:12
  • 2
    Additional note, if you are concerned: The performance hit of `instanceof` is more or less [negligible](http://stackoverflow.com/questions/103564/the-performance-impact-of-using-instanceof-in-java). What actually matters a lot more is adding `(this == obj)`. And wrt. the actual question: That depends on your ordering. How do child classes order? Because if you have two different classes that have `compareTo() == 0`, `compareTo()` violates equality (no matter for ordering, though). I usually tend to override `equals()` in subclasses, do the `instanceof` there and then delegate upwards. – dhke Jul 04 '16 at 11:14
  • It is most typically not logical or correct to write an equals method in which `child.equals(parent) == parent.equals(child)`. If you adhere to the Liskov Substitution Principle, then it is generally not possible to write a subclass with new value components that does not break the `equals()` contract with its parent. This is discussed in Effective Java, 2nd Ed. by J Bloch. – scottb Jul 04 '16 at 13:31
  • @scottb - `c.equals(p) == p.equals(c)` just means that both must be `true` or both must be `false` - so this is simply part of the equals contract - I don't see what should be wrong with this – outofmind Jul 04 '16 at 16:44
  • @outofmind: if a child class adds new value components to the hierarchy, then by definition `c.equals(p)` can never be symmetric with `p.equals(c)` because an object of type parent has no knowledge of the child's new value components. When equals is invoked on the parent object, it will never consider the child object c's new value components. A workaround is to write `equals()` merthods that return true only if the two objects are of the same class, but this violates the Liskov Substitution Principle. – scottb Jul 04 '16 at 17:43
  • @scottb - so, what's your suggestion that satisfies the equals contracts as well as the LSP? – outofmind Jul 05 '16 at 09:40

4 Answers4

1

Equality checking using instanceof only is fundamentally unsafe. If equality with subclasses doesn't make sense you need to compare the equality of the classes using this.getClass().equals(other.getClass()). If you want to allow child classes to be equal to parents (this is in many cases invalid) you have to check the relationship and delegate to the child. It is impossible for the parent to know the correct definition of equality.

class Parent {
  boolean equals(Object other) {
    if (! other instanceof Parent) return false
    if (! other.getClass().equals(Parent.class)) return other.equals(this);
    // Otherwise compare two objects that precisely have the type Parent
  }
}

Note that if the child is not final, you will also have to do that (appropriately) with the child. In the child equals, you can allow that the other is an instance of Parent (but you do need to compare equality appropriately).

Paul de Vrieze
  • 4,888
  • 1
  • 24
  • 29
  • Whether or not you should use compareTo depends on the implementation. In any case it is safer to do it on the child class (although it may be valid to do so on the parent as well - depending on the semantics). In general, I would still use the parent.equals method, as the child can then determine whether equality is valid independently from compareTo (note that the parent does not know about the child, but the child must know of the parent) – Paul de Vrieze Jul 04 '16 at 13:19
  • Thanks for directing me to `.getClass()` - this led me to a solution that fits my needs - thanks. – outofmind Jul 04 '16 at 15:09
0

Have you tried to override the equals method in child class to check the obj for parent class?

0

So how to implement .equals() using .compareTo() and guarantee child.equals(parent) == parent.equals(child)?

It seems not correct to check equality of two distinct object. You can create an interface that, implement to parent then check equality of child and parent with this interface.

Lionel Briand
  • 1,732
  • 2
  • 13
  • 21
  • Origin of the question is SonarQube which complains about this implementation being not symmetric - so I'm trying to find a solution which doesn't involve too much changes to the simple solution – outofmind Jul 04 '16 at 13:11
0

So, here's a solution that SonarQube (rule findbugs:EQ_UNUSUAL) is happy with ...

public boolean equals(Object obj)
{
    return obj != null && this.getClass().equals(obj.getClass()) && this.compareTo(obj) == 0;
}

At least this seems to be compact and should satisfy the contracts.

outofmind
  • 1,430
  • 1
  • 20
  • 37