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)); } }
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.
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:
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(); }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With