3

I am overriding the equals() method in a Java class and found a conundrum that I can't explain.

The standard equals() contract states this:

  • It is reflexive: for any reference value x, x.equals(x) should return true.
  • It is symmetric: for any reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
  • It is transitive: for any reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
  • It is consistent: for any reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the object is modified.
  • For any non-null reference value x, x.equals(null) should return false.

Therefore, the standard equals() method would be constructed like so:

public boolean equals(Object other) {
    if (null == other) return false;
    if (this == other) return true;
    if (!(other instanceof MyClassName)) return false;
    MyClassName that = (MyClassName) other;
    return this.myMemberVariable.equals(that.name);
}

Given class Foo and and class Bar extends Foo, both with a member variable String baz, the standard equals() method inside Foo looks like:

public boolean equals(Object other) {
    if (null == other) return false;
    if (this == other) return true;
    if (!(other instanceof Foo)) return false;
    Foo that = (Foo) other;
    return this.baz.equals(that.name);
}

Whereas the standard equals() method inside Bar looks like:

public boolean equals(Object other) {
    if (null == other) return false;
    if (this == other) return true;
    if (!(other instanceof Bar)) return false;
    Barthat = (Bar) other;
    return this.baz.equals(that.name);
}

If I had objects Foo foo = new Foo("Test"); and Bar bar = new Bar("Test");, and I called foo.equals(bar), this would return true. If I called bar.equals(foo), by the symmetric clause of the equals() contract, this is broken because inside equals() of Bar, (!(other instanceof Bar)) is true, making Bar's equal() method return false, which isn't correct, it should be true, by logic (both objects String baz are equal to each other), and by the symmetry clause, where if x.equals(y), y.equals(x) is inherently true.

I'm aware of the getClass() vs instanceof argument for overriding equals() and don't want another one of these arguments.

This brings me to my actual question. In such a case, how does one properly override the equals() method that follows the standard Java contract for equality?

Adam
  • 2,532
  • 1
  • 24
  • 34
  • 5
    The answer is in the question. Test if both objects have the same class. – JB Nizet Feb 26 '14 at 21:04
  • @JBNizet The contract doesn't require that they be of the same class. In this instance, it looks like the OP intended for a `Foo` to equal a `Bar` if they have the same `baz`. – Rainbolt Feb 26 '14 at 21:15
  • 2
    @John - Then why bother overriding `equals()` in the subclass? – Ted Hopp Feb 26 '14 at 21:18
  • @TedHopp Who said he should? The author himself said a `Foo` could equal a `Bar` if they share the same `baz`. `baz` is a property of the parent class, so why override `equals()` in the child class? – Rainbolt Feb 26 '14 at 21:21
  • @John: I agree. If the OP really doesn't want to change the logic of equals(), he shouldn't override it. I assumed (perhaps naively) that he had a good reason to override it. – JB Nizet Feb 26 '14 at 21:24
  • @John - I'm not saying that he should; I'm questioning why he would, given the semantics he's trying to achieve. OP is asking about "how does one properly override...". The entire question is moot unless `equals()` is being overridden. – Ted Hopp Feb 26 '14 at 21:28
  • 2
    @TedHopp The OP is asking how to PROPERLY override the equals method in a situation where it SHOULD NOT BE. The OP said that Foo's and Bar's can be equal if they share a common baz. If a Foo can equal a Baz, then Foo would, at best, override equals with an *identical* implementation. Why override an implementation with an identical implementation? – Rainbolt Feb 26 '14 at 21:30
  • @John - That was the entire point of my comment: why bother trying to override equals properly when this is a situation where it (apparently) should not be overridden at all? Your original comment didn't really make the point. – Ted Hopp Feb 26 '14 at 21:37
  • @TedHopp Don't tag ME in a question that was directed at the OP! We've wasted time and effort agreeing with one another. The question clearly looks to be directed at me. – Rainbolt Feb 26 '14 at 21:40
  • I disagree with the "close as duplicate" vote. Both questions address the same topic, but this version of the question has a far more detailed explanation of the problem, which adds significantly to our understanding. – Taryn East Feb 26 '14 at 22:31
  • @TarynEast Thank you. I also do not agree with the closure due to as the linked "duplicate" does not ask the same question as I do, and I have a specific reason for overriding the `equals()` method in the subclass `Bar`. – Adam Feb 27 '14 at 01:59
  • There are simple articles about that (http://javarevisited.blogspot.com/2011/02/how-to-write-equals-method-in-java.html)(http://javarevisited.blogspot.com/2011/02/how-to-write-equals-method-in-java.html) (http://www.artima.com/lejava/articles/equality.html)(http://www.artima.com/lejava/articles/equality.html) (http://www.javapractices.com/topic/TopicAction.do?Id=17)(http://www.javapractices.com/topic/TopicAction.do?Id=17) But don't forget about writing proper hashCode() also. – RMachnik Feb 26 '14 at 21:08
  • I've voted to reopen. To reopen it, you'll need to grab another four people to do likewise :) – Taryn East Feb 27 '14 at 22:55

1 Answers1

1

According to your question

  • Foo can equal Bar if they have the same baz
  • Foo can equal Foo if they have the same baz
  • Bar can equal Bar if they have the same baz

This clearly shows that Foo and Bar will have the exact same implementation of equals(). Therefore, you should not override it at all.

If you were to try to ignore this and override it anyway, you would reach one obvious conclusion: you can't downcast a Bar to a Foo, but you can cast both arguments to Bar. As soon as you do that, you will realize that you could have simply used .equals() from Bar.

Rainbolt
  • 3,542
  • 1
  • 20
  • 44