记录filter的一个小bug
Java 中 Objects.equals() 的类型陷阱:Long 与 Integer 的隐式比较
背景
最近在做 Code Review 时,发现了一个非常隐蔽的 bug。代码逻辑看起来完全正确,但实际运行时 filter 条件形同虚设,根本没有过滤掉任何数据。问题出在一个看似无害的 Objects.equals() 调用上。
问题代码
return response.getResult().stream()
.filter(e -> !Objects.equals(e.getOrderNum(), 0)) // ① 过滤掉 orderNum == 0 的记录
.collect(Collectors.toMap(
Employee::getId, // ② map的 key = Id
e -> e.getOrderNum() != null ? e.getOrderNum().intValue() : 0, // ③ map的 value = 派遣的任职序号
(v1, v2) -> v1)); // ④ 重复 key 取第一个
这段代码的意图很明确:过滤掉 orderNum 为 0 的记录,然后将结果收集为一个 Map。
看起来没什么问题对吧?但实际上,这个 filter 永远不会过滤掉任何记录。
原因分析
关键在于 Objects.equals(e.getOrderNum(), 0) 这一行:
e.getOrderNum()的返回类型是Long- 字面量
0会被 Java 自动装箱为Integer
所以实际比较的是一个 Long 对象和一个 Integer 对象。
我们来看 Long.equals() 的源码:
public boolean equals(Object obj) {
if (obj instanceof Long) {
return value == ((Long) obj).longValue();
}
return false;
}
Long.equals() 会先检查参数是否是 Long 类型,如果不是,直接返回 false。Integer 显然不是 Long,所以即使数值相同(都是 0),比较结果也永远是 false。
这意味着 !Objects.equals(e.getOrderNum(), 0) 永远为 true,filter 条件形同虚设。
修复方式
只需将 0 改为 0L,让字面量装箱为 Long 类型:
// ❌ 错误:0 被装箱为 Integer,与 Long 比较永远返回 false
.filter(e -> !Objects.equals(e.getOrderNum(), 0))
// ✅ 正确:0L 被装箱为 Long,类型匹配,比较正确
.filter(e -> !Objects.equals(e.getOrderNum(), 0L))
为什么这个 bug 如此隐蔽?
- 编译器不会报错:
Objects.equals(Object, Object)接受任意对象,类型不匹配不会产生编译错误 - IDE 通常也不会警告:大多数静态分析工具不会检测这种跨包装类型的比较
- 代码逻辑看起来完全正确:
orderNum != 0的意图非常清晰,很难在 Review 时发现问题 - 不会抛出异常:程序正常运行,只是结果不符合预期
举一反三
这个问题不仅限于 Long 和 Integer,所有不同包装类型之间的 equals 比较都存在同样的陷阱:
Long a = 1L;
Integer b = 1;
Short c = 1;
Objects.equals(a, b); // false —— Long vs Integer
Objects.equals(b, c); // false —— Integer vs Short
Objects.equals(a, c); // false —— Long vs Short
每个包装类的 equals() 方法都会先做 instanceof 检查,类型不同直接返回 false。
最佳实践
为了避免这类问题,建议遵循以下原则:
- 比较时显式指定字面量类型:与
Long比较用0L,与Float比较用0.0f - 优先使用基本类型比较:如果可以拆箱,用
e.getOrderNum() == 0代替Objects.equals()(注意 null 安全) - 使用 null 安全的工具方法:
// 方式一:显式类型
.filter(e -> !Objects.equals(e.getOrderNum(), 0L))
// 方式二:拆箱比较(需注意 NPE)
.filter(e -> e.getOrderNum() != null && e.getOrderNum() != 0)
// 方式三:使用 Long.valueOf
.filter(e -> !Objects.equals(e.getOrderNum(), Long.valueOf(0)))
- Code Review 时重点关注
Objects.equals()的两侧类型是否一致
总结
Java 的自动装箱机制在带来便利的同时,也埋下了不少隐患。Objects.equals() 看似是一个万能的比较方法,但当两侧的包装类型不一致时,它会静默地返回错误结果,既不报错也不警告。
这类 bug 往往在线上运行很久才被发现,因为它不会导致程序崩溃,只会导致数据结果不符合预期。希望这篇文章能帮助大家在日常开发中多一份警觉,避免踩坑。
记住:在 Java 中,
1L和1虽然数值相同,但在对象世界里,它们是完全不同的存在。

浙公网安备 33010602011771号