Arrays.asList(T... a)导致的事故

📚项目场景:

修改数据时,允许将非必填字段清空。

⛔问题描述:

由于使用的是Mybatis-Plus,只能使用LambdaUpdateWrapperUpdateWrapper通过set(column,val)来将字段清空;因为字段太多导致大量set放在一个方法,不符合部分规范,所以封装了一个SqlUtils工具来对字段进行set。

有人会问,为什么不用Mybatis-Plus@TableField(updateStrategy = FieldStrategy.IGNORED)?这不是可以直接加上注解修改的时候就会自己添加置空进去吗?

使用TableField注解确实可以达到你说的效果,但是得区分项目的整体情况;如果整个项目都是使用Mybatis-PlusLambdaUpdateWrapper来对数据的状态或者价格等字段进行更新的话,那么这个注解的可行性就少了一部分。因为有些场景下你只需要对某个字段进行更新而不是更新所有字段,例如:我这是一张申请单据,对应人送审成功,我需要更新未处理状态到处理中,这种情况下我们是不需要更新状态字段之外的其他字段,如果使用这种方式则会把其他数据清空。除非在更新前查一遍数据,然后拿这份数据去修改,这样对程序和数据库来说增加压力!

Java 复制代码
/**
 * sql处理工具
 *
 * @author Smallink *
 * @version Id: SqlUtils, v 0.1 2023/8/22 11:59 Smallink Exp $
 */
@Slf4j
@Component
public class SqlUtils<T> {

    private static final int FLAG_SERIALIZABLE = 1;

    private static final List<String> DEFAULT_EXCLUSION_FIELD = Arrays.asList("id", "createBy", "createName", "createTime", "updateBy", "updateName", "updateTime");

    /**
     * 将为空字段拼入sql
     *
     * @param wrapper 封装器
     * @param object  需要拼入对象
     * @return LambdaUpdateChainWrapper
     */
    public static UpdateWrapper packSqlUtils(UpdateWrapper<?> wrapper, Object object) {
        return (UpdateWrapper) packSqlUtils(wrapper, object, false, new ArrayList<>());
    }

    /**
     * 将为空字段拼入sql
     *
     * @param wrapper   封装器
     * @param object    需要拼入对象
     * @param fieldList 不需要拼入的字段
     * @return LambdaUpdateChainWrapper
     */
    public static UpdateWrapper packSqlUtils(UpdateWrapper<?> wrapper, Object object, List<String> fieldList) {
        return (UpdateWrapper) packSqlUtils(wrapper, object, false, CollectionUtil.isEmpty(fieldList) ? new ArrayList<>() : fieldList);
    }

    /**
     * 将为空字段拼入sql
     *
     * @param wrapper         封装器
     * @param object          需要拼入对象
     * @param ignoreNullValue 是否忽略值为空的字段
     * @param fieldList       不需要拼入的字段
     * @return LambdaUpdateChainWrapper
     */
    public static Update packSqlUtils(Update wrapper, Object object, boolean ignoreNullValue, List<String> fieldList) {
        if (ObjectUtils.isEmpty(object) || ObjectUtils.isEmpty(wrapper)) {
            log.info("mandatory parameters cannot be empty");
            throw new BizException(ErrCodeEnum.WRAPPER_OBJECT_IS_NULL_ERROR.getErrCode(), ErrCodeEnum.WRAPPER_OBJECT_IS_NULL_ERROR.getMsg());
        }

        // 添加默认排除字段
        fieldList.addAll(DEFAULT_EXCLUSION_FIELD);

        Class<?> aClass = object.getClass();
        // 将对象转换成Map
        Map<String, Object> objectMap = BeanUtil.beanToMap(object, new LinkedHashMap<>(), ignoreNullValue, key -> fieldList.contains(key) ? null : StrUtil.toUnderlineCase(key));

        // 将对象当需要修改值写入
        objectMap.forEach((key, value) -> {
            String fieldType = getFieldType(CamelUnderLineUtils.underLineToCamel(key), aClass);
            if (StringUtils.isNotEmpty(fieldType)) {
                wrapper.set(key, value);
            }
        });
        return wrapper;
    }

    // --------------------------------------------- Lambda

    /**
     * 将为空字段拼入sql
     *
     * @param wrapper 封装器
     * @param object  需要拼入对象
     * @return LambdaUpdateChainWrapper
     */
    public LambdaUpdateWrapper packLambdaSqlUtils(LambdaUpdateWrapper<T> wrapper, T object) {
        return (LambdaUpdateWrapper) packLambdaSqlUtils(wrapper, object, false, new ArrayList<>());
    }

    /**
     * 将为空字段拼入sql
     *
     * @param wrapper 封装器
     * @param object  需要拼入对象
     * @return LambdaUpdateChainWrapper
     */
    public LambdaUpdateChainWrapper packLambdaSqlUtils(LambdaUpdateChainWrapper<T> wrapper, T object) {
        return (LambdaUpdateChainWrapper) packLambdaSqlUtils(wrapper, object, false, new ArrayList<>());
    }

    /**
     * 将为空字段拼入sql
     *
     * @param wrapper   封装器
     * @param object    需要拼入对象
     * @param fieldList 不需要拼入的字段
     * @return LambdaUpdateChainWrapper
     */
    public LambdaUpdateWrapper packLambdaSqlUtils(LambdaUpdateWrapper<T> wrapper, T object, List<String> fieldList) {
        return (LambdaUpdateWrapper) packLambdaSqlUtils(wrapper, object, false, CollectionUtil.isEmpty(fieldList) ? new ArrayList<>() : fieldList);
    }

    /**
     * 将为空字段拼入sql
     *
     * @param wrapper   封装器
     * @param object    需要拼入对象
     * @param fieldList 不需要拼入的字段
     * @return LambdaUpdateChainWrapper
     */
    public LambdaUpdateChainWrapper packLambdaSqlUtils(LambdaUpdateChainWrapper<T> wrapper, T object, List<String> fieldList) {
        return (LambdaUpdateChainWrapper) packLambdaSqlUtils(wrapper, object, false, CollectionUtil.isEmpty(fieldList) ? new ArrayList<>() : fieldList);
    }

    /**
     * 将为空字段拼入sql
     *
     * @param wrapper         封装器
     * @param object          需要拼入对象
     * @param ignoreNullValue 是否忽略值为空的字段
     * @param fieldList       不需要拼入的字段
     * @return LambdaUpdateChainWrapper
     */
    public Update packLambdaSqlUtils(Update<?, SFunction<T, ?>> wrapper, T object, boolean ignoreNullValue, List<String> fieldList) {
        if (ObjectUtils.isEmpty(object) || ObjectUtils.isEmpty(wrapper)) {
            log.info("mandatory parameters cannot be empty");
            throw new BizException(ErrCodeEnum.WRAPPER_OBJECT_IS_NULL_ERROR.getErrCode(), ErrCodeEnum.WRAPPER_OBJECT_IS_NULL_ERROR.getMsg());
        }
        // 添加默认排除字段
        fieldList.addAll(DEFAULT_EXCLUSION_FIELD);

        Class<?> aClass = object.getClass();
        // 将对象转换成Map
        Map<String, Object> objectMap = BeanUtil.beanToMap(object, new LinkedHashMap<>(), ignoreNullValue, key -> fieldList.contains(key) ? null : key);
        // 将对象当需要修改值写入
        objectMap.forEach((key, value) -> {
            String fieldType = getFieldType(key, aClass);
            SFunction sFunction = StringToFunction(key, fieldType, aClass);
            wrapper.set(sFunction, value);
        });
        return wrapper;
    }

    /**
     * 将字段名转换为对应的函数式
     *
     * @param name        字段名
     * @param fieldType   字段类型
     * @param entityClass 类
     * @return
     */
    private static SFunction StringToFunction(String name, String fieldType, Class<?> entityClass) {
        Class<?> rType = getRType(fieldType);
        SFunction func;
        String getMethod = "get" + name.substring(0, 1).toUpperCase() + name.substring(1);
        final MethodHandles.Lookup lookup = MethodHandles.lookup();
        //po的返回Integer的一个方法
        MethodType methodType = MethodType.methodType(rType, entityClass);
        final CallSite site;
        try {
            //方法名叫做:getSecretLevel  转换为 SFunction function interface对象
            site = LambdaMetafactory.altMetafactory(lookup,
                    "invoke",
                    MethodType.methodType(SFunction.class),
                    methodType,
                    lookup.findVirtual(entityClass, getMethod, MethodType.methodType(rType)),
                    methodType, FLAG_SERIALIZABLE);
            func = (SFunction) site.getTarget().invokeExact();
            //数据小于这个级别的都查出来
            //    mpjLambdaWrapper.le(func, secretLevel);
            return func;
        } catch (Throwable e) {
            log.error("获取getSecretLevel方法错误", e);
        }
        return null;
    }

    /**
     * 类型转换
     *
     * @param fieldType 类型
     * @return
     */
    private static Class<?> getRType(String fieldType) {
        Class<?> rtype = null;
        switch (fieldType) {
            case "class java.lang.String":
                rtype = String.class;
                break;
            case "class java.lang.Integer":
                rtype = Integer.class;
                break;
            case "class java.lang.Double":
                rtype = Double.class;
                break;
            case "class java.lang.Boolean":
                rtype = Boolean.class;
                break;
            case "class java.util.Date":
                rtype = Date.class;
                break;
            case "class java.time.LocalDate":
                rtype = LocalDate.class;
                break;
            case "class java.time.LocalDateTime":
                rtype = LocalDateTime.class;
                break;
            case "class java.math.BigDecimal":
                rtype = BigDecimal.class;
                break;
            case "class java.lang.Long":
                rtype = Long.class;
                break;
        }
        return rtype;
    }

    /**
     * 获取字段类型
     *
     * @param fieldName 字段名
     * @param clazz     类
     * @return
     */
    private static String getFieldType(String fieldName, Class<?> clazz) {
        Field[] fields = ReflectUtils.getAllFields(clazz);
        for (Field f : fields) {
            boolean exist = false;
            TableField tableField = f.getAnnotation(TableField.class);
            if (tableField != null) {
                exist = !tableField.exist();
            }
            if (fieldName.equals(f.getName()) && !exist) {
                f.setAccessible(true);
                return f.getGenericType().toString();
            }
        }
        return null;
    }

}

工具是支持传入参数来排除指定字段修改为空(没有选择使用自定义注解,自定义注解增加了功能的复杂难度与维护,所以使用了最便捷的方式),以下是示例 需要结合SqlUtils工具看

Java 复制代码
    @Resource
    private SqlUtils sqlUtils;

    // 对象中不需要封装的字段名,
    public static List<String> SALE_QUOTE_EXCLUSION_FIELD = Arrays.asList("customerNo","customerName","quoteStatus","productCategory","productType","deliveryMethod");

    public BusinessResponse updateQuote(Object obj) {
        LambdaUpdateWrapper<Object> detailPOLambdaUpdateWrapper = new LambdaUpdateWrapper<>();
        // 调用SqlUtils工具来封装需要置空的字段
        sqlUtils.packLambdaSqlUtils(detailPOLambdaUpdateWrapper, obj, SALE_QUOTE_DATEIL_EXCLUSION_FIELD);
        detailPOLambdaUpdateWrapper.eq(BasePO::getId, id);
        baseMapper.update(new Object(), detailPOLambdaUpdateWrapper);
    }

除了指定排除字段之外,还有默认字段所以使用了add()来添加,然后就导致了异常:

乍一看,这不是空指针嘛,so easy啊(内心os:明明就有值,怎么会报这个错误呢?而且编译时也没有报错)

仔细一瞧,这UnsupportedOperationException是个什么玩意?

Java 复制代码
java.lang.UnsupportedOperationException: null
	at java.util.AbstractList.add(AbstractList.java:148) ~[na:1.8.0_151]
	at java.util.AbstractList.add(AbstractList.java:108) ~[na:1.8.0_151]

🔍原因分析:

秉持出现问题,解决问题的思想 (哈哈哈)。断点调试后,还是有问题;接下来就是看源码了,请展示:

Java 复制代码
    @SafeVarargs
    @SuppressWarnings("varargs")
    public static <T> List<T> asList(T... a) {
        return new ArrayList<>(a);
    }

    private static class ArrayList<E> extends AbstractList<E>
        implements RandomAccess, java.io.Serializable
    {
        private static final long serialVersionUID = -2764017481108945198L;
        private final E[] a;

        ArrayList(E[] array) {
            a = Objects.requireNonNull(array);
        }

        @Override
        public int size() {
            return a.length;
        }

        @Override
        public Object[] toArray() {
            return a.clone();
        }

        @Override
        @SuppressWarnings("unchecked")
        public <T> T[] toArray(T[] a) {
            int size = size();
            if (a.length < size)
                return Arrays.copyOf(this.a, size,
                                     (Class<? extends T[]>) a.getClass());
            System.arraycopy(this.a, 0, a, 0, size);
            if (a.length > size)
                a[size] = null;
            return a;
        }

        @Override
        public E get(int index) {
            return a[index];
        }

        @Override
        public E set(int index, E element) {
            E oldValue = a[index];
            a[index] = element;
            return oldValue;
        }

        @Override
        public int indexOf(Object o) {
            E[] a = this.a;
            if (o == null) {
                for (int i = 0; i < a.length; i++)
                    if (a[i] == null)
                        return i;
            } else {
                for (int i = 0; i < a.length; i++)
                    if (o.equals(a[i]))
                        return i;
            }
            return -1;
        }

        @Override
        public boolean contains(Object o) {
            return indexOf(o) != -1;
        }

        @Override
        public Spliterator<E> spliterator() {
            return Spliterators.spliterator(a, Spliterator.ORDERED);
        }

        @Override
        public void forEach(Consumer<? super E> action) {
            Objects.requireNonNull(action);
            for (E e : a) {
                action.accept(e);
            }
        }

        @Override
        public void replaceAll(UnaryOperator<E> operator) {
            Objects.requireNonNull(operator);
            E[] a = this.a;
            for (int i = 0; i < a.length; i++) {
                a[i] = operator.apply(a[i]);
            }
        }

        @Override
        public void sort(Comparator<? super E> c) {
            Arrays.sort(a, c);
        }
    }

可以从源码中看出,此ArrayList非彼java.util.ArrayListArrays.asList()使用的是类部类中的ArrayList;它继承的是AbstractList,并没有实现AbstractList里面的add()方法!!!

看到这里相信各位同学应该知道问题了,没错就是没有实现里面的add()所以导致了《空指针》异常🌚

💡问题解决:

既然知道了问题,那么就好解决了!

换一个初始化List集合的工具就行啦!

我换成了hutool的CollectionUtil.toList

📝问题总结:

经验太少...哈哈哈哈

相关推荐
Ttang23几秒前
Tomcat原理(6)——tomcat完整实现
java·tomcat
goTsHgo1 分钟前
在 Spring Boot 的 MVC 框架中 路径匹配的实现 详解
spring boot·后端·mvc
钱多多_qdd11 分钟前
spring cache源码解析(四)——从@EnableCaching开始来阅读源码
java·spring boot·spring
waicsdn_haha13 分钟前
Java/JDK下载、安装及环境配置超详细教程【Windows10、macOS和Linux图文详解】
java·运维·服务器·开发语言·windows·后端·jdk
飞的肖21 分钟前
前端使用 Element Plus架构vue3.0实现图片拖拉拽,后等比压缩,上传到Spring Boot后端
前端·spring boot·架构
Q_192849990623 分钟前
基于Spring Boot的摄影器材租赁回收系统
java·spring boot·后端
Code_流苏26 分钟前
VSCode搭建Java开发环境 2024保姆级安装教程(Java环境搭建+VSCode安装+运行测试+背景图设置)
java·ide·vscode·搭建·java开发环境
禁默1 小时前
深入浅出:AWT的基本组件及其应用
java·开发语言·界面编程
Cachel wood1 小时前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
Code哈哈笑1 小时前
【Java 学习】深度剖析Java多态:从向上转型到向下转型,解锁动态绑定的奥秘,让代码更优雅灵活
java·开发语言·学习