Apache Commons equals/hashCode builder

81,877

Solution 1

The commons/lang builders are great and I have been using them for years without noticeable performance overhead (with and without hibernate). But as Alain writes, the Guava way is even nicer:

Here's a sample Bean:

public class Bean{

    private String name;
    private int length;
    private List<Bean> children;

}

Here's equals() and hashCode() implemented with Commons/Lang:

@Override
public int hashCode(){
    return new HashCodeBuilder()
        .append(name)
        .append(length)
        .append(children)
        .toHashCode();
}

@Override
public boolean equals(final Object obj){
    if(obj instanceof Bean){
        final Bean other = (Bean) obj;
        return new EqualsBuilder()
            .append(name, other.name)
            .append(length, other.length)
            .append(children, other.children)
            .isEquals();
    } else{
        return false;
    }
}

and here with Java 7 or higher (inspired by Guava):

@Override
public int hashCode(){
    return Objects.hash(name, length, children);
}

@Override
public boolean equals(final Object obj){
    if(obj instanceof Bean){
        final Bean other = (Bean) obj;
        return Objects.equals(name, other.name)
            && length == other.length // special handling for primitives
            && Objects.equals(children, other.children);
    } else{
        return false;
    }
}

Note: this code originally referenced Guava, but as comments have pointed out, this functionality has since been introduced in the JDK, so Guava is no longer required.

As you can see the Guava / JDK version is shorter and avoids superfluous helper objects. In case of equals, it even allows for short-circuiting the evaluation if an earlier Object.equals() call returns false (to be fair: commons / lang has an ObjectUtils.equals(obj1, obj2) method with identical semantics which could be used instead of EqualsBuilder to allow short-circuiting as above).

So: yes, the commons lang builders are very preferable over manually constructed equals() and hashCode() methods (or those awful monsters Eclipse will generate for you), but the Java 7+ / Guava versions are even better.

And a note about Hibernate:

be careful about using lazy collections in your equals(), hashCode() and toString() implementations. That will fail miserably if you don't have an open Session.


Note (about equals()):

a) in both versions of equals() above, you might want to use one or both of these shortcuts also:

@Override
public boolean equals(final Object obj){
    if(obj == this) return true;  // test for reference equality
    if(obj == null) return false; // test for null
    // continue as above

b) depending on your interpretation of the equals() contract, you might also change the line(s)

    if(obj instanceof Bean){

to

    // make sure you run a null check before this
    if(obj.getClass() == getClass()){ 

If you use the second version, you probably also want to call super(equals()) inside your equals() method. Opinions differ here, the topic is discussed in this question:

right way to incorporate superclass into a Guava Objects.hashcode() implementation?

(although it's about hashCode(), the same applies to equals())


Note (inspired by Comment from kayahr)

Objects.hashCode(..) (just as the underlying Arrays.hashCode(...)) might perform badly if you have many primitive fields. In such cases, EqualsBuilder may actually be the better solution.

Solution 2

Folks, wake up! Since Java 7 there are helper methods for equals and hashCode in the standard library. Their usage is fully equivalent to usage of Guava methods.

Solution 3

If you do not want to depend on a 3rd party library (maybe you are running an a device with limited resources) and you even do not want to type your own methods, you can also let the IDE do the job, e.g. in eclipse use

Source -> Generate hashCode() and equals()...

You will get 'native' code which you can configure as you like and which you have to support on changes.


Example (eclipse Juno):

import java.util.Arrays;
import java.util.List;

public class FooBar {

    public String string;
    public List<String> stringList;
    public String[] stringArray;

    /* (non-Javadoc)
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((string == null) ? 0 : string.hashCode());
        result = prime * result + Arrays.hashCode(stringArray);
        result = prime * result
                + ((stringList == null) ? 0 : stringList.hashCode());
        return result;
    }
    /* (non-Javadoc)
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        FooBar other = (FooBar) obj;
        if (string == null) {
            if (other.string != null)
                return false;
        } else if (!string.equals(other.string))
            return false;
        if (!Arrays.equals(stringArray, other.stringArray))
            return false;
        if (stringList == null) {
            if (other.stringList != null)
                return false;
        } else if (!stringList.equals(other.stringList))
            return false;
        return true;
    }

}

Solution 4

The EqualsBuilder and HashCodeBuilder have two main aspects that are different from manually written code:

  • null handling
  • instance creation

The EqualsBuilder and HashCodeBuilder make it easier to compare fields that could be null. With manually writen code this creates a lot of boilerplate.

The EqualsBuilder will on the other hand create an instance per equals method call. If your equals methods are call often this will create a lot of instances.

For Hibernate the equals and hashCode implementation make no difference. They are just an implementation detail. For almost all domain objects loaded with hibernate the runtime overhead (even without escape analysis) of the Builder can be ignored. Database and communication overhead will be significant.

As skaffman mentioned the reflection version cannot be used in production code. Reflection will be to slow and the "implementation" will not be correct for all but the simplest classes. Taking all members into account is also dangerous as newly introduced members change the equals method behaviour. The reflection version can be useful in test code.

Solution 5

If you don't to write your own, there is also the possibility to use google guava (formerly google collections)

Share:
81,877

Related videos on Youtube

aug70co
Author by

aug70co

Updated on April 11, 2020

Comments

  • aug70co
    aug70co about 4 years

    I'm curious to know, what people here think about using org.apache.commons.lang.builder EqualsBuilder/HashCodeBuilder for implementing the equals/hashCode? Would it be a better practice than writing your own? Does it play well with Hibernate? What's your opinion?

    • skaffman
      skaffman about 13 years
      Just don't be tempted by the reflectionEquals and reflectionHashcode functions; the performance is an absolute killer.
    • digitaljoel
      digitaljoel about 13 years
      I saw some discussion on here about equals yesterday and had some free time, so I did a quick test. I had 4 objects with different equals implementations. eclipse generated, equalsbuilder.append, equalsbuilder.reflection, and pojomatic annotations. The baseline was eclipse. equalsbuilder.append took 3.7x. pojomatic took 5x. reflection based took 25.8x. It was quite discouraging because I like the simplicity of the reflection based and I can't stand the name "pojomatic".
    • Miles
      Miles over 12 years
      Another option is Project Lombok; it uses bytecode generation rather than reflection, so it should perform as well as Eclipse-generated. projectlombok.org/features/EqualsAndHashCode.html
  • Thomas Jung
    Thomas Jung about 13 years
    The same will be possible with Java 7 Objects.equals: download.oracle.com/javase/7/docs/api/java/util/…
  • digitaljoel
    digitaljoel about 13 years
    I disagree that the reflection implementation "will not be correct for all but the simplest classes." With the builders you can explicitly exclude fields if you like, so the implementation really depends on your business key definition. Unfortunately, I can't disagree with the performance aspect of the reflection based implementation.
  • Thomas Jung
    Thomas Jung about 13 years
    @digitaljoel Yes, you can exclude fields, but these definitions are not refactoring save. So I did not mention them on purpose.
  • Sean Patrick Floyd
    Sean Patrick Floyd about 13 years
    True, but the code generated by Eclipse is unreadable and unmaintainable.
  • Stephan
    Stephan over 12 years
    I like the commons-lang features, but isn't it a bad practice to create a new object on each .hashCode() or .equals() call?
  • Sean Patrick Floyd
    Sean Patrick Floyd over 12 years
    @Stephan I guess that's why the guava approach works with static methods and without Object creations (although technically the varargs invocation of Objects.hashCode generates an array Object, but there's no way around that without overloading the method n times)
  • Stephan
    Stephan over 12 years
    supplement: There is a static method hashCodeMulti(Object...) in ObjectUtils of commons-lang 3. I prefer that.
  • Sean Patrick Floyd
    Sean Patrick Floyd over 12 years
    But on the other hand, those Objects are very thin and should hardly impact performance, but they make development a lot easier
  • msteiger
    msteiger over 12 years
    It might be worth adding "if (this == obj) return true;" to avoid the actual comparison for this simple case.
  • Sean Patrick Floyd
    Sean Patrick Floyd over 12 years
    @user198397 I know, and in performance-sensitive areas I would add that. But otherwise I try to have as few code branches as possible
  • Kawu
    Kawu almost 12 years
    +1 for "be careful about using lazy collections in your equals(), hashCode() and toString() implementations. That will fail miserably if you don't have an open Session."
  • maaartinus
    maaartinus almost 12 years
    Please, never ever think about something as terrible as the eclipse-generated equals. If you don't want to depend on 3rd party library, then write the one-line method like Objects.equal yourself. Even when used only once or twice, it makes the code way better!
  • sahana
    sahana over 11 years
    There is an eclipse plugin (marketplace.eclipse.org/content/guava-eclipse-plugin) which makes eclipse use guava to generate equals, hashCode and toString. Yay, less typing and beautiful code all together!
  • FrVaBe
    FrVaBe over 11 years
    @maaartinus equals/hashCode one line methods???
  • maaartinus
    maaartinus over 11 years
    No, but public static boolean equal(Object a, Object b) {return a==b || (a!=null && a.equals(b));} from Guava is a one-liner and makes anybody repeating the null tests in their equals method look pretty stupid.
  • FrVaBe
    FrVaBe over 11 years
    @maaartinus Guava is a 3rd party library. I pointed out that my solution can be used if you want to AVOID using 3rd party libraries.
  • maaartinus
    maaartinus over 11 years
    @FrVaBe: And I wrote "If you don't want to depend on 3rd party library, then write the one-line method like Objects.equal yourself." And then I wrote the one-line method which you may use to AVOID using Guava and still cut the length of equals to about one half.
  • FrVaBe
    FrVaBe over 11 years
    @maaartinus Ok I understand what you meant. I added an example so that everybody can decide if it is too evil or not. I am really no supporter of this eclipse "help" but it is at least helpful (both methods are not as trivial as you might think).
  • maaartinus
    maaartinus over 11 years
    @FrVaBe: Good! And now compare it to dl.dropbox.com/u/4971686/published/maaartin/so/…
  • FrVaBe
    FrVaBe over 11 years
    @maaartinus Fine - Why don't you provide your solution as answer? Maybe somebody likes it :-)
  • Jeff Olson
    Jeff Olson over 11 years
    If I'm reading it correctly, Josh Bloch says in Effective Java, Item 8, that you should not use getClass() in your equals() method; rather you should use instanceof.
  • Jeff Olson
    Jeff Olson over 11 years
    @SeanPatrickFloyd - agreed, the getClass() vs instanceof debate seems to be ongoing and almost religious in nature... :)
  • kayahr
    kayahr about 11 years
    @SeanPatrickFloyd The Guava-way not only creates an array object for the varargs, it also converts ALL parameters to objects. So when you pass 10 int values to it then you will end up with 10 Integer objects and an array object. The commons-lang solution only creates a single object, no matter how many values you append to the hash code. The same problem with equals. Guava converts all values to objects, commons-lang only creates a single new object.
  • Sean Patrick Floyd
    Sean Patrick Floyd about 11 years
    @kayahr you're correct about hashcode, but for equals, when comparing primitives, I'd change the comparison to == and have that problem solved. I'll update my sample code accordingly.
  • Lyle
    Lyle over 9 years
    Those Eclipse-generated "(non-Javadoc)" comments are great because they're an easily-spotted flag that says "here's a mess that nobody bothered thinking about very much".
  • wonhee
    wonhee about 9 years
    For Apache common lang3, it's gonna be just HashCodeBuilder.reflectionHashCode(this). Way better than Guava way.
  • Sean Patrick Floyd
    Sean Patrick Floyd about 9 years
    @wonhee I strongly disagree that this is better. Using Reflection to calculate hash codes is not something I would ever do. The performance overhead is probably negligible, but it just feels wrong.
  • wonhee
    wonhee about 9 years
    It is not, and once if you have a gigantic pojo that has more than 10~20 fields in it and seeing that other people start not changing(yeah, it happens) hashCode and equals method? You'll want them to use it.
  • Sean Patrick Floyd
    Sean Patrick Floyd about 9 years
    @wonhee again, disagreed. there are more elegant solutions to this problem. Currently my favorite one is also from Google, AutoValue
  • wonhee
    wonhee about 9 years
    @SeanPatrickFloyd I disagree your disagreement. To me, it's very obvious, clear, and easy to use for HashCodeBuilder and EqualsBuilder over AutoValue. Spring and many other unit test framework is already using reflection everywhere, but wondering why you take "reflection for hashCode and equals" are wrong? I've been using IDE's hashCode and equals generation feature for a long time, but this HashCodeBuilder() was way clear and easy to maintain the code. As you mentioned, I'd say this is better than manual hashCode stuff unless there is critical performance issue.
  • kaushik
    kaushik over 7 years
    @JeffOlson, That getClass problem will be avoided if are using it in a 'final' class. Won't it ?
  • Sean Patrick Floyd
    Sean Patrick Floyd over 7 years
    @kaushik making a class final actually solves the potential problems of both versions (instanceof and getClass()), as long as you implement your equals() in leaf classes only
  • Sean Patrick Floyd
    Sean Patrick Floyd over 7 years
    a) at the time this question was asked, Java 7 wasn't there yet b) technically, they're not quite equivalent. jdk has the Objects.equals method versus Guava's Objects.equal methods. I can use static imports with Guava's version only. That's just cosmetics, I know, but it makes the non-Guava noticeably more cluttered.
  • dardo
    dardo almost 7 years
    This isn't a good method for overriding an objects equals method due to the fact that Objects.equals will call the instance's .equals method. If you call Objects.equals within the instance's .equals method it'll lead to a stack overflow.
  • Mikhail Golubtsov
    Mikhail Golubtsov almost 7 years
    Can you give an example, when it falls into a loop?
  • dardo
    dardo almost 7 years
    OP is asking for overriding the equals() method within an Object. As per the documentation of the static method Objects.equals(): "Returns true if the arguments are equal to each other and false otherwise. Consequently, if both arguments are null, true is returned and if exactly one argument is null, false is returned. Otherwise, equality is determined by using the equals method of the first argument." Therefore, if you used Objects.equals() within the overridden instance equals() it'd call it's own equals method, then Objects.equals() then itself again, giving a stack overflow.
  • Mikhail Golubtsov
    Mikhail Golubtsov almost 7 years
    @dardo We are speaking about implementing structural equality, so it means two objects are equal to each other if their fields do. See the Guava example above, how equals is implemented.
  • dardo
    dardo almost 7 years
    I understand that, what I'm saying is you're misunderstanding the initial question. What the question is asking is how to implement the actual equals method within an object. Objects.equals invokes the equals method, so this wouldn't be a viable option since it would then result in a stack overflow.
  • Mikhail Golubtsov
    Mikhail Golubtsov almost 7 years
    Please read the top answer carefully. In my answer I'm referring to it (and it mentions Guava, though the question was about Apache Commons). I just wanted to say, that there is already a good way to solve this problem using the standard library without any of those two.
  • Till Schäfer
    Till Schäfer about 6 years
    Note that the Guava way is now (since java 7) implemented in the JDK itself. E.g., you can use java.util.Objects.equals and java.util.Objects.hash.
  • Sean Patrick Floyd
    Sean Patrick Floyd about 6 years
    @TillSchäfer this answer came before Java 7. But as you are not the only one pointing this out, I have updated the answer to reference Java 7+. Thanks
  • Sean Patrick Floyd
    Sean Patrick Floyd about 6 years
    @Scorpion your edit is incorrect. In Guava, it was Objects.equal(), but since this answer now uses java.util.Objects, it's Object.equals(), which may be stupid, but unfortunately correct.