Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overriding Proper Java equals method

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?

like image 411
Adam Avatar asked Mar 01 '26 19:03

Adam


1 Answers

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.

like image 134
Rainbolt Avatar answered Mar 04 '26 08:03

Rainbolt



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!