Lombok: EqualsAndHashCode work with BigDecimal ?

Created on 14 Dec 2016  路  12Comments  路  Source: projectlombok/lombok

Is it possible using compareTo rather than equals to compare BigDecimal ?

Now, I have to create a specific method equals for BigDecimal objects and add all of them in exclude of @EqualsAndHashCode.

I found some discussion about that problem :
http://stackoverflow.com/questions/36625347/how-to-make-lomboks-equalsandhashcode-work-with-bigdecimal
http://tech.transferwise.com/fun-with-bigdecimal/
https://groups.google.com/forum/#!searchin/project-lombok/BigDecimal|sort:relevance/project-lombok/oRdyHiecgWE/GHLLhL8FYdIJ

Maybe just an option in annotation @EqualsAndHashCode for switching between equals and compareTo for some kind of classes...

Most helpful comment

I don't think this issue should be closed.

It would be great to be able to provide a specific equals method for given fields. Probably like the OP, I have a deep structure with a few Big Decimal here and there and it fails equals if one BigDecimal field is 1 and the other one 1.0

All 12 comments

Unfortunately, this is not possible. Also, if you put them in an array or collection, that also wouldn't work.

We are looking into a system to provide method for specific fields, but we're not there yet.

Oh, by the way, beware that if the equals returns true, the hashCodes should also be the same. How would you do that?

We are looking into a system to provide method for specific fields, but we're not there yet.

AFAIK the problem is lacking a suitable interface in rt.jar. The problem could be solved the other way round: Use something else in place of a field value (think writeReplace or readResolve). Annotating a field like @EqHC(use=someMethod) isn't refactoring-friendly (), but we don't have to specify that "someMethod" gets used exactly *instead of "someField". Just exclude "someField" and include "someMethod" like in

@EqualsAndHashCode
class MyClass {

    @EqualsAndHashCode.Exclude
    private BigDecimal myDecimal;

    @EqualsAndHashCode.Include
    private Object myDecimalForEqHC() {
        return myDecimal.stripTrailingZeros();
    }
}

It's a bit verbose, but still much shorter and cleaner than writing your own. It also solves #1223 and this idea could be used as a workaround for #1262 and other issues where a single field prevents using Lombok for the whole class. Just allow no-args methods to be used as if they were fields.

Oh, by the way, beware that if the equals returns true, the hashCodes should also be the same. How would you do that?

The OP's idea of using compareTo don't seem to work together with hashCode, but using equals/hashCode on a no-arg method's result is fine.


(*) I'm assuming you want to deprecate of and exclude some day.

I don't think this issue should be closed.

It would be great to be able to provide a specific equals method for given fields. Probably like the OP, I have a deep structure with a few Big Decimal here and there and it fails equals if one BigDecimal field is 1 and the other one 1.0

I'd also vote for reopening this issue

Is this issue going to be fixed anytime in future? Because equals generated by lombok isn't very useful in case we have some fields of BigDecimal. That would be great if this equal check on BigDecimal replaced by compareTo.

@nihadibrahimli I don't think that anything should or can be changed as the current behavior is the most correct possible. But now, there's a workaround, working exactly as described years ago.

We are looking into a system to provide method for specific fields, but we're not there yet.

AFAIK the problem is lacking a suitable interface in rt.jar. The problem could be solved the other way round: Use something else in place of a field value (think writeReplace or readResolve). Annotating a field like @EqHC(use=someMethod) isn't refactoring-friendly (*), but we don't have to specify that "someMethod" gets used exactly _instead of_ "someField". Just exclude "someField" and include "someMethod" like in

@EqualsAndHashCode
class MyClass {

    @EqualsAndHashCode.Exclude
    private BigDecimal myDecimal;

    @EqualsAndHashCode.Include
    private Object myDecimalForEqHC() {
        return myDecimal.stripTrailingZeros();
    }
}

It's a bit verbose, but still much shorter and cleaner than writing your own. It also solves #1223 and this idea could be used as a workaround for #1262 and other issues where a _single field_ prevents using Lombok for the _whole class_. Just allow no-args methods to be used as if they were fields.

Oh, by the way, beware that if the equals returns true, the hashCodes should also be the same. How would you do that?

The OP's idea of using compareTo don't seem to work together with hashCode, but using equals/hashCode on a no-arg method's result is fine.

(*) I'm assuming you want to deprecate of and exclude some day.

Please note that this solution is not null-safe.
A null-safe solution would be:

@EqualsAndHashCode.Include
private BigDecimal equalsMyDecimal() {
   return myDecimal != null ? myDecimal.stripTrailingZeros() : null;
}

Why not Lombok generates exactly how it is explained by @Maaartinus? Ok, maybe with some options on the annotation @EqualsAndHashCode(bigDecimalMessAvoided=true) ?

Just guessing:
1) It extends the annotation with a parameter that more than 99% of the Lombok users will never need. It could lead to confusion for users, and increases the maintenance costs for the Lombok team.

2) There is an easy workaround. Even better, this workaround _makes it clearly visible_ that you deviate from the regular equals, which makes it (imo) an even better solution than an additional parameter.

If you're using BigDecimal in your POJO you'll want correctly generated HC&E methods that ignores scale. That should be more that 99% of developers. When adding new BigDecimal attribute you need to make extra work and is actually more work than when not using Lombok(2 extra annotations and an extra method when on manually will be 2 lines - one in equals, one in hashCode).
OK, I agree it's not clearly that standard HC&E rules does not apply but developer should be aware of this when using BigDecimal.

It would be great to be able to provide a specific equals method for given fields.

This feature remains closed; __the solution Maaartinus posted is already part of lombok__. You don't even have to use the exclude option:

@EqualsAndHashCode
public class Example {
    private BigDecimal bd;

    @EqualsAndHashCode.include
    private BigDecimal bd() {
        return bd == null ? null : bd.stripTrailingZeroes();
    }
}

though Maaartinus' approach is presumably more readable (a private method named 'bd' doesn't do a great job at communicating what it is for via its name).

Why not Lombok generates exactly how it is explained by @Maaartinus? Ok, maybe with some options on the annotation @EqualsAndHashCode(bigDecimalMessAvoided=true) ?

Because this is impossible:

class Example {
    private Object[] ohDear = new Object[] { someBigDecimal };
}

how can lombok possibly figure out that calling Arrays.equals(this.ohDear, other.ohDear) is going to give a wrong result? The same argument can be made for a List<BigDecimal> - do you really want lombok to figure out that it is a list of BigDecimal and thus, that lombok should explicitly iterate through both this.list and other.list to do BD cleanup before comparing/hashcoding? That's... impossible - custom impls of collection concepts exist and lombok cannot possibly be explicitly hardcoded to recognize all of these. Thus, avoidBigDecimalMess = true would be a lie; it would only apply to fields of the BD type itself.

Separately, as janrieke said, if we add avoidBigDecimalMess = true, then I can give you 50 other such exotic features and we (@rspilker and I) are quite certain that this would be a lot worse even without thinking of the crazy maintenance burden that implies.

Silently doing the right thing is an option, except:

  1. It'd be real hard to document: Lombok silently ignores trailing zeroes and relative scale issues for fields of type BD, but __not__ for fields of any other type which so happen to be referencing BigDecimal instances (private Object o = new BigDecimal(...);), nor if BDs show up in arrays or lists, even if the array/list is properly typed.
  2. Is stripping trailing zeroes and ignoring scale the right move? If it was so simple, and all java programmers are universally in agreement that this is the correct equals/hc treatment, then why isn't BigDecimal's equals and hashCode implemented to function like this? The truth seems pretty clear to me: There are different ways to think about / use BigDecimal, in particular, to think about what 'equality' means for BDs, and what you are asking for is just one of multiple views. Silently doing the right think means lombok is dictating a different idea on what it means vs. what it does now, which would be backwards incompatible.

Thus, it'd be incorrect to silently do this, and it's also incorrect to add a setting or annoparam, thus, this feature remains denied.

Use @EqualsAndHashCode.Include, solves all your problems and also explicitly makes clear in your source code what 'take' on equality you're looking for for your BD field.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

lombokissues picture lombokissues  路  39Comments

lex-em picture lex-em  路  61Comments

krzyk picture krzyk  路  88Comments

rspilker picture rspilker  路  98Comments

ilgrosso picture ilgrosso  路  76Comments