Mystery0の小站

Mystery0の小站

如何在Java中校验一个对象是否为空

2019-11-23

原因

SpringBoot 项目中,我们通过对应的 JavaBean 来接收前端传来的 Json 数据,然后执行我们自己的业务逻辑,特殊一点的比如更新接口,我们一般是判断 JavaBean 对象的属性来决定对应的更新SQL,得益于各种框架,我们不用再手动做这种麻烦的事情。

我司项目在下一版本中提出了一个要求:当用户只修改了某一个字段,那么执行不同的逻辑,否则执行更新逻辑。看到这里,我想各位都想的是反射,毕竟一个个的判断属性太low了。 在这里先不论如何实现这个,我突然发现一个问题,如果前端调用更新接口,传过来一个空的json对象,那么会怎么样?

复现

我司使用的 MyBatis(Plus) 来处理数据库相关的操作,因为是更新接口,为了以后方便记录用户的更新日志,所以前端都是用户修改了什么就只传什么过来,自然没办法在参数上添加校验器,由此引申出问题:如果用户传的是 null 或者 {} ,那么会发生什么。

整个service中都没有处理这种情况,最后进入到了 MyBatis 部分,然后执行失败,生成的SQL语句变为了:

update xxx  where id = ?

思考

原因很简单,更新的 Entity 中,因为之后 id 不为空(更新接口id是放在url中的),其他的都为 null ,所以生成了这样子的SQL,这自然是不对的,所以我们需要在 Service 或者 Controller 中进行处理。

So. How to detect this situation?

思路1

反射应该是我们第一个想到的,毕竟一个个的 if 判断太low了。 于是,有了如下代码:

     /**
     * 检测对象的属性是否全部为空
     *
     * @param object 对象
     * @return true: 属性全部为空 false: 属性有的不为空
     */
    private <T> boolean isAllFieldsNull(@Nullable T object) throws IllegalAccessException {
        if (object == null) {
            return true;
        }
        for (Field declaredField : object.getClass().getDeclaredFields()) {
            declaredField.setAccessible(true);
            if (declaredField.get(object) != null) {
                return false;
            }
        }
        return true;
    }

简单,明了。 但是,是存在问题的!!!

如果 object 中存在默认值呢?那么就校验通过了(当然,某些情况下这个也不属于错误,具体看各位的需求)。

还有就是 serialVersionUID 或者说只考虑了引用类型,基本类型没考虑。

思路2

既然想到了上面的问题,所以就自然有对应的解决方法。

得益于 LarryZeal文章,我完美的跳过了这个步骤,所以在这里,我就只贴上 LarryZeal 的代码。

/**
     * 目的,判断是否有赋值。
     * 不能传入null对象!!!
     * 
     * @param obj
     * @return
     * @throws Exception
     */
    public <T> boolean isAllFieldStill(T obj) throws Exception {
        Class cls = obj.getClass();
        T t2 = (T) cls.getConstructor().newInstance();// 创建一个新的对象,用于对比数据
        Field[] fields = cls.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true); // 访问所有字段
            // if (field.getName().equals("serialVersionUID")) {// 无视掉序列化ID
            // continue;
            // }
            if (field.get(obj) != null) { // 卧槽,基本类型做不到啊
                if (field.getType().getName().equals("byte") || field.getType().getName().equals("short")
                        || field.getType().getName().equals("int") || field.getType().getName().equals("long")) {
                    // 判断
                    if ((int) field.get(obj) != (int) field.get(t2)) {
                        return false;
                    }
                } else if (field.getType().getName().equals("boolean")) {
                    // 判断
                    if ((boolean) field.get(obj) != (boolean) field.get(t2)) {
                        return false;
                    }
                } else if (field.getType().getName().equals("float")) {
                    // 判断
                    if ((float) field.get(obj) != (float) field.get(t2)) {
                        return false;
                    }
                } else if (field.getType().getName().equals("double")) {
                    // 判断
                    if ((double) field.get(obj) != (double) field.get(t2)) {
                        return false;
                    }
                } else {
                    // 都是引用类型了(引用类型需要有equals方法)
                    // 判断....
                    if (!field.get(obj).equals(field.get(t2))) {
                        return false;
                    }
                }
                return false;
            }

        }

        return true;
    }

思路3

直接使用新的对象然后使用 equals 判断不就行了?

按理说应该是行的,但是我试了……不行

换一种思路

既然前端提交的是 json 串,那么我们能不能从 json 角度去判断呢?

于是有了以下代码:

    /**
     * 判断对象的所有属性是否为空
     *
     * @param value 对象
     * @param <T>   范型
     * @return true: 全部属性为空 false: 全部属性不为空
     */
    public static <T> boolean isAllFieldNull(@Nullable T value) {
        if (value == null) {
            return true;
        }
        try {
            String origin = JSON.toJSONString(value);
            String empty = JSON.toJSONString(value.getClass().newInstance());
            return origin.equals(empty);
        } catch (Exception e) {
            log.warn("verify object failed", e);
            return true;
        }
    }

但是 这种方法是有一定条件的,我们都知道不同的 json 处理器对空数据的判定不一样,没有这个属性算不算null?字符串值是 "" 算不算 null ?有这个属性但是值是null算不算null?

我们只需要定义好自己的 json 处理逻辑,然后将需要判断的对象以及使用反射创建的一个空对象转换成 json 数据,只要 json 处理逻辑没问题,判断两个字符串是否相等就行了。

延伸

判断List中所有的数据是否为空

    /**
     * 判断集合的所有成员是否为空
     *
     * @param collection 集合
     * @param <T>        范型
     * @return true: 全部成员为空 false: 全部成员中有的不为空
     */
    public static <T> boolean isAllElementNull(@Nullable Collection<T> collection) {
        if (collection == null) {
            return true;
        }
        if (CollectionUtils.isEmpty(collection)) {
            return true;
        }
        for (T t : collection) {
            boolean result = isAllFieldNull(t);
            if (result) {
                return false;
            }
        }
        return true;
    }

感谢列表

如何判断一个对象的内容是否为空