forked from jqno/equalsverifier
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add BigDecimal equality using compareTo checks
- Loading branch information
Showing
10 changed files
with
328 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
--- | ||
title: "`BigDecimal` equality using `compareTo(BigDecimal val)`" | ||
permalink: /manual/bigdecimal-compareto/ | ||
--- | ||
The `Comparable` interface strongly recommends but does not require that implementations consider two objects equal using `compareTo` whenever they are equal using `equals` and vice versa. `BigDecimal` is a class where this is not applied. | ||
|
||
{% highlight java %} | ||
BigDecimal zero = new BigDecimal("0"); | ||
BigDecimal alsoZero = new BigDecimal("0.0"); | ||
|
||
// prints true - zero is the same as zero | ||
System.out.println(zero.compareTo(alsoZero) == 0); | ||
// prints false - zero is not the same as zero | ||
System.out.println(zero.equals(alsoZero)); | ||
{% endhighlight %} | ||
|
||
This is because `BigDecimal` can have multiple representations of the same value. It uses an unscaled value and a scale so, for example, the value of 1 can be represented as unscaled value 1 with scale of 0 (the number of places after the decimal point) or as unscaled value 10 with scale of 1 resolving to 1.0. Its `equals` and `hashCode` methods use both of these attributes in their calculation rather than the resolved value. | ||
|
||
If your class contains any `BigDecimal` fields, and you would like comparably equal values to be considered the same, then the `equals` method must use `compareTo` for the check and the `hashCode` calculation must derive the same value for all `BigDecimal` instances that are equal using `compareTo` (taking care if it is a nullable field). | ||
|
||
EqualsVerifier can check this by adding `usingBigDecimalCompareTo()`: | ||
|
||
{% highlight java %} | ||
EqualsVerifier.forClass(FooWithComparablyEqualBigDecimalFields.class) | ||
.usingBigDecimalCompareTo() | ||
.verify(); | ||
{% endhighlight %} | ||
|
||
There is more information on `compareTo` and `equals` in the `Comparable` Javadoc and Effective Java's chapter on implementing `Comparable`. | ||
There is more information on `BigDecimal` in its Javadoc (and its representation can be seen by printing `unscaledValue()` and `scale()`). |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
84 changes: 84 additions & 0 deletions
84
src/main/java/nl/jqno/equalsverifier/internal/checkers/fieldchecks/BigDecimalFieldCheck.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
package nl.jqno.equalsverifier.internal.checkers.fieldchecks; | ||
|
||
import static nl.jqno.equalsverifier.internal.util.Assert.assertEquals; | ||
|
||
import java.lang.reflect.Field; | ||
import java.math.BigDecimal; | ||
import java.math.RoundingMode; | ||
import nl.jqno.equalsverifier.internal.reflection.FieldAccessor; | ||
import nl.jqno.equalsverifier.internal.reflection.ObjectAccessor; | ||
import nl.jqno.equalsverifier.internal.util.CachedHashCodeInitializer; | ||
import nl.jqno.equalsverifier.internal.util.Formatter; | ||
|
||
public class BigDecimalFieldCheck<T> implements FieldCheck<T> { | ||
|
||
private final CachedHashCodeInitializer<T> cachedHashCodeInitializer; | ||
|
||
public BigDecimalFieldCheck(CachedHashCodeInitializer<T> cachedHashCodeInitializer) { | ||
this.cachedHashCodeInitializer = cachedHashCodeInitializer; | ||
} | ||
|
||
@Override | ||
public void execute( | ||
ObjectAccessor<T> referenceAccessor, | ||
ObjectAccessor<T> copyAccessor, | ||
FieldAccessor fieldAccessor | ||
) { | ||
if (BigDecimal.class.equals(fieldAccessor.getFieldType())) { | ||
Field field = fieldAccessor.getField(); | ||
BigDecimal referenceField = (BigDecimal) referenceAccessor.getField(field); | ||
BigDecimal changedField = referenceField.setScale( | ||
referenceField.scale() + 1, | ||
RoundingMode.UNNECESSARY | ||
); | ||
ObjectAccessor<T> changed = copyAccessor.withFieldSetTo(field, changedField); | ||
|
||
T left = referenceAccessor.get(); | ||
T right = changed.get(); | ||
|
||
checkEquals(field, referenceField, changedField, left, right); | ||
checkHashCode(field, referenceField, changedField, left, right); | ||
} | ||
} | ||
|
||
private void checkEquals( | ||
Field field, | ||
BigDecimal referenceField, | ||
BigDecimal changedField, | ||
T left, | ||
T right | ||
) { | ||
Formatter f = Formatter.of( | ||
"BigDecimal equality by comparison: object does not equal a copy of itself where BigDecimal field %%" + | ||
" has a value that is equal using compareTo: %% compared to %%" + | ||
"\nIf these values should be considered equal then use compareTo rather than equals for this field." + | ||
"\nIf these values should not be considered equal, then remove usingBigDecimalCompareTo() to disable this check.", | ||
field.getName(), | ||
referenceField, | ||
changedField | ||
); | ||
assertEquals(f, left, right); | ||
} | ||
|
||
private void checkHashCode( | ||
Field field, | ||
BigDecimal referenceField, | ||
BigDecimal changedField, | ||
T left, | ||
T right | ||
) { | ||
Formatter f = Formatter.of( | ||
"BigDecimal equality by comparison: hashCode of object does not equal hashCode of a copy of itself" + | ||
" where BigDecimal field %%" + | ||
" has a value that is equal using compareTo: %% compared to %%" + | ||
"\nIf these values should be considered equal then make sure to derive the same constituent hashCode from this field." + | ||
"\nIf these values should not be considered equal, then remove usingBigDecimalCompareTo() to disable this check.", | ||
field.getName(), | ||
referenceField, | ||
changedField | ||
); | ||
int leftHashCode = cachedHashCodeInitializer.getInitializedHashCode(left); | ||
int rightHashCode = cachedHashCodeInitializer.getInitializedHashCode(right); | ||
assertEquals(f, leftHashCode, rightHashCode); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.