开发者

Apache Commons equals/hashCode builder [closed]

Closed. This question is opinion-based. It is not currently accepting answers.

Want to improve this question? Update the question so it can be answered with facts and citations by editing this post.

Closed 5 years ago.

开发者_如何转开发 Improve this question

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?


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.


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.


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;
    }

}


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.


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


If you are just dealing with the entity bean where id is a primary key, you can simplify.

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

      EntityBean castOther = (EntityBean) other;
      return new EqualsBuilder().append(this.getId(), castOther.getId()).isEquals();
   }


In my opinion it doesn't play well with Hibernate, especially the examples from the answer comparing length, name and children for some entity. Hibernate advises to use business key to be used in equals() and hashCode(), and they have their reasons. If you use auto equals() and hashCode() generator on your business key, it's ok, just the performance problems need to be considered as mentioned previously. But people usually uses all properties what is IMO very wrong. For example I'm currently working on project where entities are written using Pojomatic with @AutoProperty, what I consider a really bad pattern.

Their two main scenarios to use hashCode() and equals() are:

  • when you put instances of persistent classes in a Set (the recommended way to represent many-valued associations) and
  • when you use reattachment of detached instances

So let's assume our entity looks like this:

class Entity {
  protected Long id;
  protected String someProp;
  public Entity(Long id, String someProp);
}

Entity entity1 = new Entity(1, "a");
Entity entity2 = new Entity(1, "b");

Both are the same entity for Hibernate, which have been fetched from some session at some point (their id and class/table are equal). But when we implement auto equals() a hashCode() on all props, what do we have?

  1. When you put the entity2 to the persistent set where the entity1 already exists, this will be put twice and will result in exception during commit.
  2. If you want to attach the detached entity2 to the session, where entity1 already exists they (probably, I haven't tested this especially) won't be merged properly.

So, for 99% project I make, we use the following implementation of equals() and hashCode() written once in base entity class, which is consistent with the Hibernate concepts:

@Override
public boolean equals(Object obj) {
    if (StringUtils.isEmpty(id))
        return super.equals(obj);

    return getClass().isInstance(obj) && id.equals(((IDomain) obj).getId());
}

@Override
public int hashCode() {
    return StringUtils.isEmpty(id)
        ? super.hashCode()
        : String.format("%s/%s", getClass().getSimpleName(), getId()).hashCode();
}

For the transient entity I do the same what Hibernate will do on persistence step, ie. I use the instance match. For the persistent objects I compare the unique key, which is the table/id (I never use composite keys).


Just in case, others will find it useful, I've come up with this Helper class for hash code computation that avoids extra object creation overhead mentioned above (in fact, the overhead of Objects.hash() method is even bigger when you have inheritance as it will create an new array on each level!).

Usage example:

public int hashCode() {
    return HashCode.hash(HashCode.hash(timestampMillis), name, dateOfBirth); // timestampMillis is long
}

public int hashCode() {
    return HashCode.hash(super.hashCode(), occupation, children);
}

The HashCode helper:

public class HashCode {

    public static int hash(Object o1, Object o2) {
        return add(Objects.hashCode(o1), o2);
    }

    public static int hash(Object o1, Object o2, Object o3) {
        return hash(Objects.hashCode(o1), o2, o3);
    }

    ...

    public static int hash(Object o1, Object o2, ..., Object o10) {
        return hash(Objects.hashCode(o1), o2, o3, ..., o10);
    }

    public static int hash(int initial, Object o1, Object o2) {
        return add(add(initial, o1), o2);
    }

    ...

    public static int hash(int initial, Object o1, Object o2, ... Object o10) {
        return add(... add(add(add(initial, o1), o2), o3) ..., o10);
    }

    public static int hash(long value) {
        return (int) (value ^ (value >>> 32));
    }

    public static int hash(int initial, long value) {
        return add(initial, hash(value));
    }

    private static int add(int accumulator, Object o) {
        return 31 * accumulator + Objects.hashCode(o);
    }
}

I've figured that 10 is the maximum reasonable number of properties in a domain model, if you have more you should think of refactoring and introducing more class instead of maintaining a heap of Strings and primitives.

The drawbacks are: it's not useful if you have mainly primitives and/or arrays that you need to hash deeply. (Normally this is the case when you have to deal with flat (transfer) objects that is out of your control).

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜