一边传的是 1.0,另一边传的是 1.00。你对着账单看了半天,金额确实没变。代码一跑,equals 却给了个 false。同样两笔数据塞进 HashSet,最后还真留下两条。

这件事不用翻业务代码。拿 JDK 17 或 21 跑个小例子就能看明白。

import java.math.BigDecimal;
import java.util.HashSet;
import java.util.List;
import java.util.TreeSet;

public class AmountCheck {
    public static void main(String[] args) {
        var left = new BigDecimal("1.0");
        var right = new BigDecimal("1.00");

        System.out.println(left.equals(right));
        System.out.println(left.compareTo(right));
        System.out.println(new HashSet<>(List.of(left, right)).size());
        System.out.println(new TreeSet<>(List.of(left, right)).size());
    }
}

保存成 AmountCheck.java,执行 java AmountCheck.java。四行结果依次是 false、0、2、1。

差的就是最后那个零

用字符串构造时,1.0 的 scale 是 1,1.00 的 scale 是 2。BigDecimal.equals 同时看数值和 scale,所以它俩不相等。compareTo 只看数值大小,返回 0 就表示这两个金额在数值上相等。

如果你只是想知道同一币种的订单金额变没变,用 oldAmount.compareTo(newAmount) == 0 判断。钱的单位也得先对上,两张账单里的 1.00 可能一个是元,一个是美元。

改了比较方式,为什么集合里还是两条

HashSet 用的是元素的 hashCode 和 equals。你把对账那一行改成 compareTo,不会改变集合怎么算重复。默认的 TreeSet 则按自然顺序判断是否是同一个集合元素,两个数比较结果为零时只留一条。它留下的是先放进去的那个对象,原来的小数位还在。

碰到金额要当 HashMap 的键,先把业务规则定下来。比如字段约定只接受能精确到两位小数的金额,就在进集合前统一一下。把这个方法加到类里,顶部补上 import java.math.RoundingMode;。

static BigDecimal amountKey(String raw) {
    return new BigDecimal(raw).setScale(2, RoundingMode.UNNECESSARY);
}

"1.0" 和 "1.00" 都会得到 1.00。如果来了 "1.001",方法会抛出 ArithmeticException,调用方可以按输入错误处理。要是业务允许更细的计价,先确认在哪一步舍入、按什么规则舍入,再决定用什么做键。

复测就用那两条对账数据。先看金额判断,再把它们走一遍实际的去重或查询。要是金额来自接口,顺手看一下绑定字段的类型,尤其是中间有没有先转成 double。 Spring Boot 接收 JSON 参数

参考资料