HashCodeBuilder and EqualsBuilder usage style

49,095

Solution 1

Of course the second option is more elegant and simple. But if you are concerned about performance you should go for first approach. Second method also fails if a security manager is running. I would go for the first option if I was in your situation.

Also there is a mistake in your first approach in generating hashCode:
It should be builder.toHashCode() instead of builder.hashCode(). The latter returns hashcode builder object's hash code.

Solution 2

Even though the second option is more attractive (because it is just one line of code) I would choose the first option.

The reason is simply performance. After running a small test I found a very great time difference between them.

In order to sort of get an idea of time, I created this two simple classes:

package equalsbuildertest;

import java.math.BigDecimal;
import java.util.Date;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;

public class Class1 {

    private int field1;

    private boolean field2;

    private BigDecimal field3;

    private String field4;

    private Date field5;

    private long field6;

    public Class1(int field1, boolean field2, BigDecimal field3, String field4,
            Date field5, long field6) {
        super();
        this.field1 = field1;
        this.field2 = field2;
        this.field3 = field3;
        this.field4 = field4;
        this.field5 = field5;
        this.field6 = field6;
    }

    public Class1() {
        super();
    }

    public int getField1() {
        return field1;
    }

    public void setField1(int field1) {
        this.field1 = field1;
    }

    public boolean isField2() {
        return field2;
    }

    public void setField2(boolean field2) {
        this.field2 = field2;
    }

    public BigDecimal getField3() {
        return field3;
    }

    public void setField3(BigDecimal field3) {
        this.field3 = field3;
    }

    public String getField4() {
        return field4;
    }

    public void setField4(String field4) {
        this.field4 = field4;
    }

    public Date getField5() {
        return field5;
    }

    public void setField5(Date field5) {
        this.field5 = field5;
    }

    public long getField6() {
        return field6;
    }

    public void setField6(long field6) {
        this.field6 = field6;
    }

    @Override
    public boolean equals(Object o) {
      return EqualsBuilder.reflectionEquals(this, o);
    }

    @Override
    public int hashCode() {
        return HashCodeBuilder.reflectionHashCode(this);
    }

}

And:

package equalsbuildertest;

import java.math.BigDecimal;
import java.util.Date;

import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;

public class Class2 {

    private int field1;

    private boolean field2;

    private BigDecimal field3;

    private String field4;

    private Date field5;

    private long field6;

    public Class2(int field1, boolean field2, BigDecimal field3, String field4,
            Date field5, long field6) {
        super();
        this.field1 = field1;
        this.field2 = field2;
        this.field3 = field3;
        this.field4 = field4;
        this.field5 = field5;
        this.field6 = field6;
    }

    public Class2() {
        super();
    }

    public int getField1() {
        return field1;
    }

    public void setField1(int field1) {
        this.field1 = field1;
    }

    public boolean isField2() {
        return field2;
    }

    public void setField2(boolean field2) {
        this.field2 = field2;
    }

    public BigDecimal getField3() {
        return field3;
    }

    public void setField3(BigDecimal field3) {
        this.field3 = field3;
    }

    public String getField4() {
        return field4;
    }

    public void setField4(String field4) {
        this.field4 = field4;
    }

    public Date getField5() {
        return field5;
    }

    public void setField5(Date field5) {
        this.field5 = field5;
    }

    public long getField6() {
        return field6;
    }

    public void setField6(long field6) {
        this.field6 = field6;
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Class2)) {
            return false;
        }
        Class2 other = (Class2) obj;
        EqualsBuilder builder = new EqualsBuilder();
        builder.append(field1, other.field1);
        builder.append(field2, other.field2);
        builder.append(field3, other.field3);
        builder.append(field4, other.field4);
        builder.append(field5, other.field5);
        builder.append(field6, other.field6);
        return builder.isEquals();
    }

    @Override
    public int hashCode() {
        HashCodeBuilder builder = new HashCodeBuilder();
        builder.append(getField1());
        builder.append(isField2());
        builder.append(getField3());
        builder.append(getField4());
        builder.append(getField5());
        builder.append(getField6());
        return builder.hashCode();

    };

}

That second class is pretty much the same as the first one, but with different equals and hashCode.

After that, I created the following tests:

package equalsbuildertest;

import static org.junit.Assert.*;

import java.math.BigDecimal;
import java.util.Date;

import org.junit.Test;

public class EqualsBuilderTest {

    @Test
    public void test1() {
        Class1 class1a = new Class1(1, true, new BigDecimal(0), "String", new Date(), 1L);
        Class1 class1b = new Class1(1, true, new BigDecimal(0), "String", new Date(), 1L);
        for (int i = 0; i < 1000000; i++) {
            assertEquals(class1a, class1b);
        }
    }

    @Test
    public void test2() {
        Class2 class2a = new Class2(1, true, new BigDecimal(0), "String", new Date(), 1L);
        Class2 class2b = new Class2(1, true, new BigDecimal(0), "String", new Date(), 1L);
        for (int i = 0; i < 1000000; i++) {
            assertEquals(class2a, class2b);
        }
    }

}

The test are pretty simple and only serves to measure time.

The results were the following:

  • test1 (2,024 s)
  • test2 (0,039 s)

I chose them to be completely equal in order to have the greatest times. If you choose to do the test with NotEquals conditions you will have shorter time, but keeping a very large time difference too.

I run this tests on a 64-bit Intel Core i5-3317U CPU @1.70GHz x4 with Fedora 21 and Eclipse Luna.

In conclusion, I would not risk such a great performance difference in order to save a couple of lines of code that you can possibly not type anyway using a template (in Eclipse under Windows -> Preferences is found in Java -> Editor -> Templates) such as this:

${:import(org.apache.commons.lang3.builder.HashCodeBuilder, org.apache.commons.lang3.builder.EqualsBuilder)}
@Override
public int hashCode() {
    HashCodeBuilder hashCodeBuilder = new HashCodeBuilder();
    hashCodeBuilder.append(${field1:field});
    hashCodeBuilder.append(${field2:field});
    hashCodeBuilder.append(${field3:field});
    hashCodeBuilder.append(${field4:field});
    hashCodeBuilder.append(${field5:field});
    return hashCodeBuilder.toHashCode();
}

@Override
public boolean equals(Object obj) {
    if (this == obj) {
        return true;
    }
    if (obj == null) {
        return false;
    }
    if (getClass() != obj.getClass()) {
        return false;
    }
    ${enclosing_type} rhs = (${enclosing_type}) obj;
    EqualsBuilder equalsBuilder = new EqualsBuilder();
    equalsBuilder.append(${field1}, rhs.${field1});
    equalsBuilder.append(${field2}, rhs.${field2});
    equalsBuilder.append(${field3}, rhs.${field3});
    equalsBuilder.append(${field4}, rhs.${field4});
    equalsBuilder.append(${field5}, rhs.${field5});${cursor}
    return equalsBuilder.isEquals();
}

Solution 3

I would prefer the second option for 2 reasons:

  1. Obviously it is easier to read

  2. I wouldn't buy performance arguments for the first option, unless those include a relevant metric. E.g. how many milliseconds would reflection-based "equals" add to a typical end-to-end request latency? Overall, what % increase would that be? Without knowing that good chances are that the optimization is premature

Solution 4

Your question as written clearly illustrates one of the benefits of the second approach:

In the first case, it's very easy to make the mistake return builder.hashCode(), instead of the correct return builder.toHashCode(), which will result in subtle errors that can be very hard to track down.

The second case eliminates the possibility of this typo, resulting in less banging your head into the keyboard trying to find the bug.

Solution 5

I would say neither one of these are good implementation. I would argue that EqualsBuilder is not a good framework to use for the following reasons:

  1. Not extendable. What if one of the field you are trying to assert equality should treat nulls and blank as equal?
  2. You must maintain the list of variable as if they are hardcoded variables. Meaning you must list all the variables you want to compare. At this point there isn't any different between a == o.getA() && b == o.getB() ...
  3. Using reflection takes up additional resource as you have pointed out, and in an enterprise application that crush billions of objects. Doing this reflection equals is as bad as having a memory leak.

I'll say there needs to be a better framework than the Apache one.

Share:
49,095
tintin
Author by

tintin

Updated on November 05, 2020

Comments

  • tintin
    tintin over 3 years

    I often use apache HashCodeBuilder and EqualsBuilder for object equality using reflection, but recently I a colleague told me that using reflection may cause a huge performance hit if the entity contains lots of properties. Worried that I may be using a wrong implementation, my question is, which of the following approach would you prefer? And why?

    public class Admin {
    
        private Long id;
        private String userName;
    
        public String getUserName() {
            return userName;
        }
    
        @Override
        public boolean equals(Object o) {
            if (!(o instanceof Admin)) {
                return false;
            }
            Admin otherAdmin  = (Admin) o;
            EqualsBuilder builder = new EqualsBuilder();
            builder.append(getUserName(), otherAdmin.getUserName());
            return builder.isEquals();
        }
    
        @Override
        public int hashCode() {
            HashCodeBuilder builder = new HashCodeBuilder();
            builder.append(getUserName());
            return builder.hashCode();
        }
    }
    

    Vs.

    public class Admin {
    
        private Long id;
        private String userName;
    
        public String getUserName() {
            return userName;
        }
    
        @Override
        public boolean equals(Object o) {
          return EqualsBuilder.reflectionEquals(this, o, Arrays.asList(id));
        }
    
        @Override
        public int hashCode() {
            return HashCodeBuilder.reflectionHashCode(this, Arrays.asList(id));
        }
    }