[
https://issues.apache.org/jira/browse/LANG-467?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=12642958#action_12642958
]
davey.jones edited comment on LANG-467 at 10/28/08 2:10 AM:
------------------------------------------------------------
The use of compareTo() in EqualsBuilder() is arguably wrong, however compareTo
is referenced from the BigDecimal javadoc as the alternative to equals for
cases where the scale is not relevant
In LANG-393 it was decided that EqualsBuidler should use the compareTo() method
to compare BigDecimals(), which is a nice convenience for those of use using
BigDecimals in conjunction with EqualsBuilder and who want 0 and 0.0 to be
considered equal.
However LANG-393 did not put the equivalent fix into the HashCodeBuilder so
this has made HashCodeBuilder and EqualsBuilder non-symmetric. It was actually
LANG-393 which added a feature to EqualsBuilder and at the same time created
this bug in HashCodeBuilder.
So as far as the EqualsBuilder is concerned 10.2 and 10.20 are equal, i.e. the
following evaluates to true.
{code}
new EqualsBuilder().append(new BigDecimal("10.2"), new
BigDecimal("10.20")).isEquals();
{code}
However when using these two values with HashCodeBuilder they actually give
different hashCodes(), the following evaluates to false.
{code}
new HashCodeBuilder(17, 37).append(new BigDecimal("10.2")).toHashCode() == new
HashCodeBuilder(17, 37).append(new BigDecimal("10.20")).toHashCode()
{code}
However the contract of hashCode() method for Object says that if two objects
are considered equal using their equals method then they must also generate the
same hashCode().
Of course this is true for BigDecimal class itself, even though it is somewhat
inconvenient.
MyPojo class as given in the test case above
* implements the equals() method as documented by EqualsBuilder
* implements the hashCode() method as documented by HashCodeBuilder
Despite following the documented approach for implementing equals and hashCode
the test case *proves* that MyPojo breaks the contract of hashCode(), the
following evaluates to true
{code}
myPojo1.equals(myPojo2)
{code}
However myPojo1 and myPojo2 generate different hashCodes(), the following
evaluates to false
{code}
myPojo1.hashCode() == myPojo2.hashCode()
{code}
was (Author: davey.jones):
The use of compareTo() in EqualsBuilder() is arguably wrong, however
compareTo is referenced from the BigDecimal javadoc as the alternative to
equals for cases where the scale is not relevant
In LANG-393 it was decided that EqualsBuidler should use the compareTo() method
to compare BigDecimals(), which is a nice convenience for those of use using
BigDecimals in conjunction with EqualsBuilder and who want 0 and 0.0 to be
considered equal.
However LANG-393 did not put the equivalent fix into the HashCodeBuilder so
this has made HashCodeBuilder and EqualsBuilder non-symmetric. It was actually
LANG-393 which added a feature to EqualsBuilder and at the same time created
this bug in HashCodeBuilder.
So as far as the EqualsBuilder is concerned 10.2 and 10.20 are equal, i.e. the
following evaluates to true.
{code}
new EqualsBuilder().append(new BigDecimal("10.2"), new
BigDecimal("10.20")).isEquals();
{code}
However when using these two values with HashCodeBuilder they actually give
different hashCodes(), the following evaluates to false.
{code}
new HashCodeBuilder(17, 37).append(new BigDecimal("10.2")) == new
HashCodeBuilder(17, 37).append(new BigDecimal("10.20"))
{code}
However the contract of hashCode() method for Object says that if two objects
are considered equal using their equals method then they must also generate the
same hashCode().
Of course this is true for BigDecimal class itself, even though it is somewhat
inconvenient.
MyPojo class as given in the test case above
* implements the equals() method as documented by EqualsBuilder
* implements the hashCode() method as documented by HashCodeBuilder
Despite following the documented approach for implementing equals and hashCode
the test case *proves* that MyPojo breaks the contract of hashCode(), the
following evaluates to true
{code}
myPojo1.equals(myPojo2)
{code}
However myPojo1 and myPojo2 generate different hashCodes(), the following
evaluates to false
{code}
myPojo1.hashCode() == myPojo2.hashCode()
{code}
> EqualsBuilder and HashCodeBuilder treat java.math.BigDecimal inconsistantly
> and break general contract of hashCode
> ------------------------------------------------------------------------------------------------------------------
>
> Key: LANG-467
> URL: https://issues.apache.org/jira/browse/LANG-467
> Project: Commons Lang
> Issue Type: Bug
> Affects Versions: 2.4
> Reporter: David Jones
> Priority: Minor
>
> A POJO with a BigDecimal field and equals() and hashCode() methods
> implemented using EqualsBuilder and HashCodeBuilder breaks the general
> contract of Object.hashCode();
> EqualsBuilder treats BigDecimal specially by comparing it using
> BigDecimal.compareTo() == 0 rather than BigDecimal.equals()
> Using BigDecimal.compareTo() ignores the scale of the BigDecimal()
> However the append(Object o) method of HashCodeBuilder uses
> BigDecimal.hashCode() in the case that o is a BigDecimal, which takes the
> scale into account when generating the hashCode.
> The following test case shows the problem!
> {code:title=TestApacheCommonsLangHashCodeBuilder.java|borderStyle=solid}
> // package declaration and imports not shown
> public class TestApacheCommonsLangHashCodeBuilder extends TestCase {
>
> public void testHashCode() {
> MyPojo myPojo1 = new MyPojo(new String("foo"), new
> BigDecimal("10.2"));
> MyPojo myPojo2 = new MyPojo(new String("foo"), new
> BigDecimal("10.20"));
>
> // equals method ignores the scale of the big decimal
> // so this test passes
> assertTrue(myPojo1.equals(myPojo2));
>
> // however in the case the equals returns true the
> // hashCode must be the same according to the contract
> // TEST FAILS AT THIS LINE
> assertEquals(myPojo1.hashCode(), myPojo2.hashCode());
> }
>
> private class MyPojo {
> private String name;
> private BigDecimal value;
>
> public MyPojo(String name, BigDecimal value) {
> this.name = name;
> this.value = value;
> }
>
> public String getName() {
> return name;
> }
> public BigDecimal getValue() {
> return value;
> }
> /**
> * equals method implemented using EqualsBuilder
> * as documented by apache commons
> */
> @Override public boolean equals(Object obj) {
> if(this == obj) {
> return true;
> }
>
> if(!(obj instanceof MyPojo)) {
> return false;
> }
>
> MyPojo other = (MyPojo) obj;
> return new EqualsBuilder()
> .append(name, other.getName())
> .append(value, other.getValue())
> .isEquals();
> }
>
> /**
> * hashCode method implemented using HashCodeBuilder
> * as documented by apache commons
> */
> @Override public int hashCode() {
> HashCodeBuilder hcb = new HashCodeBuilder(17, 31);
> return hcb
> .append(name)
> .append(value)
> .toHashCode();
> }
> }
> }
> {code}
> Note that the only reason I haven't provided a patch is because I could not
> think of one which I thought was reasonable.
> *Option 1*
> Always set the scale to some value and then get the hashCode()
> Example change in HashCodeBuilder.append(Object) add the following:
> {code}
> else if (object instanceof BigDecimal) {
> append(((BigDecimal) object).setScale(DEFAULT_BIGDECIMAL_SCALE,
> RoundingMode.DOWN).hashCode());
> }
> {code}
> However what is a reasonable scale for calculating this hashCode? I cannot
> see a reasonanble scale to choose, that depends on the circumstance
> *Option 2*
> stripTrailingZeros() before calculating the hashCode()
> Example change in HashCodeBuilder.append(Object) add the following:
> {code}
> else if (object instanceof BigDecimal) {
> append(((BigDecimal) object).stripTrailingZeros().hashCode());
> }
> {code}
> The performance of this method under different circumstances is not
> documented.
> *Option 3*
> Document the problem and propose that the client solves the problem.
> For example change HashCodeBuilder documentation as follows
> {code}
> /*
> * ...
> * public class Person {
> * String name;
> * int age;
> * boolean smoker;
> * BigDecimal netWorth;
> * ...
> *
> * public int hashCode() {
> * // you pick a hard-coded, randomly chosen, non-zero, odd number
> * // ideally different for each class
> * return new HashCodeBuilder(17, 37).
> * append(name).
> * append(age).
> * append(smoker).
> * // take special care when using BigDecimal as scale takes
> * // is included in the hashCode calculation breaking hashCode contract
> * // choose a scale which is reasonable for hashCode calculation
> * append(netWorth == null ? null : netWorth.setScale(2)).
> * toHashCode();
> * }
> * }
> * ...
> */
> {code}
--
This message is automatically generated by JIRA.
-
You can reply to this email to add a comment to the issue online.