ObjectUtils.nullSafeEquals你真的用对了吗?

引言

在写代码时,我们通常喜欢使用org.springframework.util.ObjectUtils#nullSafeEquals来比较两个对象是否相等,从而避免使用equals方法在对象为空时导致空指针异常。
最近在写代码时,我试图使用stream流的filter,配合使用ObjectUtils.nullSafeEquals过滤出租户id不为0的数据。于是写了类似如下的代码。在具体讲述问题之前,我们先看下代码,大家判断下代码会输出什么?

public static void main(String[] args) {
   List<Long> tenantIds = new ArrayList<>();
   tenantIds.add(0L);
   tenantIds.add(1L);
   tenantIds.add(2L);
   List<Long> list = tenantIds.stream()
           .filter(id -> !ObjectUtils.nullSafeEquals(id, 0))
           .collect(Collectors.toList());
   System.out.println(list);
}

OK,我们运行下代码揭晓下答案,竟然并没有过滤掉为0的id。
在这里插入图片描述

排查

那为啥没有过滤掉0呢?我们来看下ObjectUtils.nullSafeEquals的相关源码。
在这里插入图片描述
方法其实很直观,我们一行一行看下。

public static boolean nullSafeEquals(@Nullable Object o1, @Nullable Object o2) {
    // 比较的两个对象引用地址相同,说明对象为同一个。返回true
    if (o1 == o2) {
        return true;
    }
    // 如果两个对象存在为null的,则返回false
    if (o1 == null || o2 == null) {
        return false;
    }
    // 由于上面的if校验,说明o1不为null,这里调用equals方法比较两个对象是否相等
    if (o1.equals(o2)) {
        return true;
    }
    // 这里是比较两个数组对象,调用了arrayEquals方法比较
    if (o1.getClass().isArray() && o2.getClass().isArray()) {
        return arrayEquals(o1, o2);
    }
    return false;
}

看完上述方法,再根据我们传入的参数。我们可以定位到问题发生在o1.equals(o2),那这里为啥返回了false?
我们知道equals方法为Object类的方法,所有类的父类。我们在编写自定义类时,通常会重写equals方法来实现当前类的比较。

由于咱们上面定义的tenantId为Long类型,那我们找到Long类中的equals方法,看看它做了什么就能知道原因了?源码如下
在这里插入图片描述
我们可以看到这里判断了传入的obj如果是Long类型或其子类,会把传入的对象强转为Long类型,调用其中的longValue方法与当前的Long对象维护的value进行比较。
ObjectUtils.nullSafeEquals(id, 0) 这里的两个0为啥不相等呢?我们简化下代码 ,使用IDEA中jclasslib插件看下字节码文件(这个插件推荐下)。

在这里插入图片描述

简化后的代码:

public static void main(String[] args) {
    Long tenantId = 0L;
    System.out.println(ObjectUtils.nullSafeEquals(tenantId, 0));
}

字节码

 0 lconst_0
 1 invokestatic #2 <java/lang/Long.valueOf : (J)Ljava/lang/Long;>
 4 astore_1
 5 getstatic #3 <java/lang/System.out : Ljava/io/PrintStream;>
 8 aload_1
 9 iconst_0
10 invokestatic #4 <java/lang/Integer.valueOf : (I)Ljava/lang/Integer;>
13 invokestatic #5 <org/springframework/util/ObjectUtils.nullSafeEquals : (Ljava/lang/Object;Ljava/lang/Object;)Z>
16 invokevirtual #6 <java/io/PrintStream.println : (Z)V>
19 return

我们可以看到ObjectUtils.nullSafeEquals(id, 0) 中的0,在编译时,会被编译器转换为 Integer的包装类。从而导致了运行结果与预期不符合的场景。

那我们怎么解决呢?
我们可以把ObjectUtils.nullSafeEquals(id, 0)修改为ObjectUtils.nullSafeEquals(id, 0L),这样编译器就会把0L转换为Long类型的包装类,从而就能正确调用Long类的equals方法了。

思考

  1. 我们平时开发时,通常会犯一些很低级的错误,从而导致系统的bug出现。只是一个租户id为0的一条数据未能正确过滤,也许就会导致生产事故的发生。在去年,我所在的项目就遇到了一个空串导致的p0级别的生产事故,导致了很多数据异常删除,最后处理了一两天才完全把数据恢复好。这个后面有时间会作为负面教材给大家分享出来。
  2. 我们在使用一些轮子(方法)时,不要想当然的去使用,一定要有阅读源码的习惯,并做一些单元测试,来确保方法结果符合预期。
  3. 在编写代码时,一定要尽量考虑周全,提高代码的健壮性,该判空的时候不要觉得数据不可能为空就不去写兜底判断,对自己代码负责一些。
  4. 这个代码其实排查起来很快的,但是考虑到也许有刚入行的同学不明白,就给大家分享的细致了些,希望大家都能有所收获。
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雨雨雨就要爆炸了

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值