1. 概述
有时我们需要忽略数值的具体类型或类来比较它们。当数据格式不统一,且数值可能在不同场景下使用时,这种比较方式特别有用。
本文将探讨如何比较基本类型和不同类的数值(如Integer
、Long
、Float
等),以及如何比较浮点数与整数。
2. 比较不同的类
2.1. 比较整数基本类型
Java 提供了多种表示整数的基本类型。为简化说明,我们主要讨论 int
、long
和 double
。 使用基本类型时,可以直接比较数值是否相等:
@ValueSource(strings = {"1", "2", "3", "4", "5"})
@ParameterizedTest
void givenSameNumbersButDifferentPrimitives_WhenCheckEquality_ThenTheyEqual(String number) {
int integerNumber = Integer.parseInt(number);
long longNumber = Long.parseLong(number);
assertEquals(longNumber, integerNumber);
}
⚠️ 但这种方法在溢出时不可靠。以下测试会正确识别数值不相等:
@ValueSource(strings = {"1", "2", "3", "4", "5"})
@ParameterizedTest
void givenSameNumbersButDifferentPrimitivesWithIntegerOverflow_WhenCheckEquality_ThenTheyNotEqual(String number) {
int integerNumber = Integer.MAX_VALUE + Integer.parseInt(number);
long longNumber = Integer.MAX_VALUE + Long.parseLong(number);
assertNotEquals(longNumber, integerNumber);
}
❌ 但如果两个值同时溢出,可能导致错误结果。 虽然不容易踩坑,但以下场景仍需警惕:
@Test
void givenSameNumbersButDifferentPrimitivesWithLongOverflow_WhenCheckEquality_ThenTheyEqual() {
long longValue = BigInteger.valueOf(Long.MAX_VALUE)
.add(BigInteger.ONE)
.multiply(BigInteger.TWO).longValue();
int integerValue = BigInteger.valueOf(Long.MAX_VALUE)
.add(BigInteger.ONE).intValue();
assertThat(longValue).isEqualTo(integerValue);
}
此测试会认为两数相等,尽管其中一个实际是另一个的两倍。 这种方法只适用于小数值且无溢出风险的情况。
2.2. 比较整数和浮点基本类型
比较整数与浮点数时情况类似:
@ValueSource(strings = {"1", "2", "3", "4", "5"})
@ParameterizedTest
void givenSameNumbersButDifferentPrimitivesTypes_WhenCheckEquality_ThenTheyEqual(String number) {
int integerNumber = Integer.parseInt(number);
double doubleNumber = Double.parseDouble(number);
assertEquals(doubleNumber, integerNumber);
}
这是因为 int
会被自动提升为 double
或 float
。 但只要数值存在微小差异,比较结果就符合预期:
@ValueSource(strings = {"1", "2", "3", "4", "5"})
@ParameterizedTest
void givenDifferentNumbersButDifferentPrimitivesTypes_WhenCheckEquality_ThenTheyNotEqual(String number) {
int integerNumber = Integer.parseInt(number);
double doubleNumber = Double.parseDouble(number) + 0.0000000000001;
assertNotEquals(doubleNumber, integerNumber);
}
⚠️ 但精度和溢出问题依然存在。 即使比较同类型数值,结果也可能不可靠:
@Test
void givenSameNumbersButDifferentPrimitivesWithDoubleOverflow_WhenCheckEquality_ThenTheyEqual() {
double firstDoubleValue = BigDecimal.valueOf(Double.MAX_VALUE).add(BigDecimal.valueOf(42)).doubleValue();
double secondDoubleValue = BigDecimal.valueOf(Double.MAX_VALUE).doubleValue();
assertEquals(firstDoubleValue, secondDoubleValue);
}
以下场景更直观:用浮点数表示百分比(1=100%)与整数表示百分比(30)的比较:
@Test
void givenSameNumbersWithDoubleRoundingErrors_WhenCheckEquality_ThenTheyNotEqual() {
double doubleValue = 0.3 / 0.1; // 结果为 2.9999999999999996
int integerValue = 30 / 10; // 结果为 3
assertNotEquals(doubleValue, integerValue);
}
因此,基本类型比较不可靠,尤其涉及浮点运算时。
3. 比较包装类
使用包装类时,结果与基本类型不同:
@ValueSource(strings = {"1", "2", "3", "4", "5"})
@ParameterizedTest
void givenSameNumbersButWrapperTypes_WhenCheckEquality_ThenTheyNotEqual(String number) {
Float floatNumber = Float.valueOf(number);
Integer integerNumber = Integer.valueOf(number);
assertNotEquals(floatNumber, integerNumber);
}
尽管 Float
和 Integer
数值相同,但比较结果为不相等。 即使比较 Integer
和 Long
也是如此:
@ValueSource(strings = {"1", "2", "3", "4", "5"})
@ParameterizedTest
void givenSameNumbersButDifferentWrappers_WhenCheckEquality_ThenTheyNotEqual(String number) {
Integer integerNumber = Integer.valueOf(number);
Long longNumber = Long.valueOf(number);
assertNotEquals(longNumber, integerNumber);
}
核心问题在于比较 Number
层次结构中的不同类。 大多数类的 equals()
方法会先检查类型是否相同,例如 Long
的实现:
public boolean equals(Object obj) {
if (obj instanceof Long) {
return value == ((Long)obj).longValue();
}
return false;
}
这种设计避免了传递性问题,但也导致不同表示形式的数值无法直接比较。
4. BigDecimal
BigDecimal
是解决精度问题的最佳选择。我们分两种情况测试:相同精度和不同精度:
static Stream<Arguments> numbersWithDifferentScaleProvider() {
return Stream.of(
Arguments.of("0", "0.0"), Arguments.of("1", "1.0"),
Arguments.of("2", "2.0"), Arguments.of("3", "3.0"),
Arguments.of("4", "4.0"), Arguments.of("5", "5.0"),
Arguments.of("6", "6.0"), Arguments.of("7", "7.0")
);
}
static Stream<Arguments> numbersWithSameScaleProvider() {
return Stream.of(
Arguments.of("0", "0"), Arguments.of("1", "1"),
Arguments.of("2", "2"), Arguments.of("3", "3"),
Arguments.of("4", "4"), Arguments.of("5", "5"),
Arguments.of("6", "6"), Arguments.of("7", "7")
);
}
✅ 相同精度的比较符合预期:
@MethodSource("numbersWithSameScaleProvider")
@ParameterizedTest
void givenBigDecimalsWithSameScale_WhenCheckEquality_ThenTheyEqual(String firstNumber, String secondNumber) {
BigDecimal firstBigDecimal = new BigDecimal(firstNumber);
BigDecimal secondBigDecimal = new BigDecimal(secondNumber);
assertEquals(firstBigDecimal, secondBigDecimal);
}
❌ 但不同精度时结果不同:
@MethodSource("numbersWithDifferentScaleProvider")
@ParameterizedTest
void givenBigDecimalsWithDifferentScale_WhenCheckEquality_ThenTheyNotEqual(String firstNumber, String secondNumber) {
BigDecimal firstBigDecimal = new BigDecimal(firstNumber);
BigDecimal secondBigDecimal = new BigDecimal(secondNumber);
assertNotEquals(firstBigDecimal, secondBigDecimal);
}
BigDecimal
认为 1
和 1.0
不相等! 因为 equals()
会比较精度(scale),即使只是末尾零不同。
✅ 解决方案:使用 compareTo()
方法忽略精度差异:
@MethodSource("numbersWithDifferentScaleProvider")
@ParameterizedTest
void givenBigDecimalsWithDifferentScale_WhenCompare_ThenTheyEqual(String firstNumber, String secondNumber) {
BigDecimal firstBigDecimal = new BigDecimal(firstNumber);
BigDecimal secondBigDecimal = new BigDecimal(secondNumber);
assertEquals(0, firstBigDecimal.compareTo(secondBigDecimal));
}
BigDecimal
是最佳选择,但需注意 equals()
和 compareTo()
的行为差异。
5. AssertJ
使用 AssertJ 可简化断言代码:
@MethodSource("numbersWithDifferentScaleProvider")
@ParameterizedTest
void givenBigDecimalsWithDifferentScale_WhenCompareWithAssertJ_ThenTheyEqual(String firstNumber, String secondNumber) {
BigDecimal firstBigDecimal = new BigDecimal(firstNumber);
BigDecimal secondBigDecimal = new BigDecimal(secondNumber);
assertThat(firstBigDecimal).isEqualByComparingTo(secondBigDecimal);
}
此方法更简洁,也支持自定义比较器处理复杂逻辑。
6. 结论
✅ 最佳实践总结:
- 基本类型比较:仅适用于小数值且无溢出风险
- 包装类比较:不同类直接比较始终返回
false
- **
BigDecimal
**:- 使用
compareTo()
忽略精度差异 - 避免踩坑
equals()
的精度检查
- 使用
- AssertJ:推荐使用
isEqualByComparingTo()
简化代码
核心原则:忽略类型比较数值时,BigDecimal
+ compareTo()
是最可靠的方案。
所有示例代码可在 GitHub 获取。