记录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 类型,如果不是,直接返回 falseInteger 显然不是 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 如此隐蔽?

  1. 编译器不会报错Objects.equals(Object, Object) 接受任意对象,类型不匹配不会产生编译错误
  2. IDE 通常也不会警告:大多数静态分析工具不会检测这种跨包装类型的比较
  3. 代码逻辑看起来完全正确orderNum != 0 的意图非常清晰,很难在 Review 时发现问题
  4. 不会抛出异常:程序正常运行,只是结果不符合预期

举一反三

这个问题不仅限于 LongInteger所有不同包装类型之间的 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 中,1L1 虽然数值相同,但在对象世界里,它们是完全不同的存在。

posted @ 2026-04-01 13:53  cwp0  阅读(2)  评论(0)    收藏  举报