views:

217

answers:

4

I read this SO post after I wrote out the title but still decided to go through with the question on bug-proof implementations of equals in Java. This is my normal implementation

@Override
        public boolean equals(Object o){
            if(o == null) return false;
            if(o instanceof CompositePk == false) return false;
            if(this == o) return true;
            CompositePk that = (CompositePk)o;
            return new EqualsBuilder().append(this.id, that.id)
                                      .append(this.bucketId, that.bucketId)
                                      .isEquals();
        }

using Apache's EqualsBuilder to do the mundane stuff. Even easier than this is my Netbean's automatically generated equals(o) implementation

 @Override
        public boolean equals(Object obj){
        if (obj == null) {
                return false;
            }
            if (getClass() != obj.getClass()) {
                return false;
            }
            final TemplatesWrapper other = (TemplatesWrapper) obj;
            if (this.timeAdded != other.timeAdded && (this.timeAdded == null || !this.timeAdded.equals(other.timeAdded))) {
                return false;
            }
            return true;
    }

I take these from 2 diff projects but they both try to accomplish the same thing but using diff approaches. Which style would you rather or are there any flaws you spot?

+4  A: 

I would do it this way:

public boolean equals(Object ob) {
  if (ob == null) return false;
  if (ob == this) return true;

  if (!(ob instanceof MyClass)) return false; // OR
  if (ob.getClass() != getClass()) return false;

  // check relevant members
}

The two lines in the middle are different. One allows for subclasses to be equal (the first one), the other doesn't. Use whichever one is appropriate.

To give you an example, Java's AbstractList class will probably use the second form, because the exact implementation of List is irrelevant. what matters is if the members are equal and in the same position.

Conversely, a Person class should use the first form (instanceof) because if there is a Student subclass and you call Person.equals(Student) it may return true without checking the extra fields in Student whereas Student.equals(Person) will probably return false. If equals() isn't commutative, you're asking for trouble.

I tend to use equals() methods generated by my IDE (IntelliJ IDEA) rather than creating an unnecessary dependency to some Apache library for little gain.

cletus
I prefer to use Apache Commons EqualsBuilder for this reason: it is less maintenance to use when you're adding new fields. However, you _can_ argue that you'd simply get IDEA to regenerate `equals` each time you add fields, and that'd be a fair point too. To each their own. :-P
Chris Jester-Young
You know about the `||` operator? ;-)
Dave Jarvis
A: 

Apache's is better than yours or cletus'.

As far as my vague memory suggests, there is a problem with using instanceof in equals; I can't quite put my finger on why yet, perhaps someone will elaborate. I could be wrong.

-- Edit:

As Chris and Steve helpfully explain below, I was thinking of the "symmetric property" of equals implementation. On this basis, I can back up my claim of prefering the Apache implementation :)

Noon Silk
The trouble with using `instanceof` is it's asymmetric if you're comparing an object one class A with an object of class B, where B subclasses A. But to do "class equality" comparison is even worse; then subclasses are never considered equal to superclass instances, even when substitutability says that they should be equal. i.e., `instanceof` is the lesser of two evils.
Chris Jester-Young
Using instanceof violates equals' symmetric property. That is, x.equals(y) is true if and only if y.equals(y) is true. This is comparing the class (using getClass) is technically correct.
Steve Kuo
Steve: Yes, that's it. Thanks. Amused that I've been downvoted for bringing this up, but regardless, you've explained it, so all is well :)
Noon Silk
-1 This is a complete non-answer "A is better than B (no justification" and "Theres some problem with A, not sure what". Why even answer?
cletus
@silky: I brought it up first and you didn't acknowledge me? That's it, eat another -1. (Just kidding; not wasting my rep point over this.) :-P
Chris Jester-Young
cletus: To bring up an important point :) He asked which is better, I answered. The question is clearly "Which style would you rather or are there any flaws you spot?", and this is a direct answer.
Noon Silk
Chris: Your response was too wordy, it fried my poor brain, upon second reading you are both acknowledged! Upvotes for all!
Noon Silk
@silky: *lol* I normally never kick up a fuss about it, but given that my conclusion is the opposite of Steve's (namely, that substitutability is more important to uphold than symmetry), I felt that my point should be heard too. :-P
Chris Jester-Young
@silky: Incidentally I agree that using Apache Commons EqualsBuilder is the best approach too, so have a +1 for that. :-P
Chris Jester-Young
@silky: Thanks for correcting the spelling of my name. :-P
Chris Jester-Young
Chris: No worries, I laughed to myself when I noticed I somehow misread/wrote it "Chester"; I was thinking "who on earth is that guy?".
Noon Silk
+7  A: 

First of all, there's no need to test for null, then test for instanceof, since foo instanceof Bar evaluates to false when foo is null.

It's weird to compare the result of the instanceof operator to false, since instanceof is a boolean operation.

Comparing classes with getClass() is at best controversial. Joshua Bloch, who wrote much of the Java collections framework and a lot of other important stuff besides, says

This technique ("getClass-based equals methods") does satisfy the equals contract, but at great cost. The disadvantage of the getClass approach is that it violates the "Liskov Substitution Principle," which states (roughly speaking) that a method expecting a superclass instance must behave properly when presented with a subclass instance. If a subclass adds a few new methods, or trivially modifies behavior (e.g., by emitting a trace upon each method invocation), programmers will be surprised when subclass and superclass instances don't interact properly. Objects that "ought to be equal" won't be, causing programs to fail or behave erratically. The problem is exacerbated by the fact that Java's collections are based on the equals method.

You should use instanceof instead of comparing via getClass() unless you have some specific technical reason not to.

After establishing that the other object is comparable to this, you then compare primitives with == and objects with equals. It's more complicated if any of your member objects can be null; you must then write verbose clauses to compare possibly null things to each other (or write a bothNullOrEqual(Object a, Object b) method).

The EqualsBuilder approach looks bogus to me, but that's just a "smell", which I won't argue against technically. In general, I don't like extra method calls in a method that may be called frequently.

The Apache one is bogus because it tests for null and uses the getClass() comparison.

Here's mine:

@Override
public boolean equals(final Object o) {
    if (!(o instanceof MyClass))
        return false;
    final MyClass om = (MyClass)o;
    // compare om's fields to mine
}
Jonathan Feinberg
`EqualsBuilder`, at least for the version I have, doesn't do `getClass` comparison unless two arrays are being compared, which is actually fair game (in most cases).
Chris Jester-Young
What's the final for? Are you afraid that you might reassign om?
Steve Kuo
`final` is an aid to reasoning. It's one less thing to think about as you read further on. I wish everything in Java were `final` unless marked `mutable`.
Jonathan Feinberg
Link to Bloch on `equals` (Effective Java 1st ed Chapter 3): http://java.sun.com/developer/Books/effectivejava/Chapter3.pdf
McDowell
A: 

Honestly, the less code you have to write, the better off you are (in most cases).

The code that's generated has been debugged and used by many MANY people. You might as well use what's generated (and if you need to enhance the performance, do so).

The advantage of using the generated code: any time your instance fields changes (and this generated code wasn't modified), you can simply regenerate code.

Sometimes, it's easier to think about maintainability. Rule of thumb: the less code you write yourself, the less you have to debug. If the generated code doesn't take a huge performance hit, generate it!

vinnybad