本文主要是介绍Arrays.asList(T... a)导致的事故,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
📚项目场景:
修改数据时,允许将非必填字段清空。
⛔问题描述:
由于使用的是Mybatis-Plus
,只能使用LambdaUpdateWrapper
或UpdateWrapper
通过set(column,val)
来将字段清空;因为字段太多导致大量set放在一个方法,不符合部分规范,所以封装了一个SqlUtils
工具来对字段进行set。
有人会问,为什么不用Mybatis-Plus
的@TableField(updateStrategy = FieldStrategy.IGNORED)
?这不是可以直接加上注解修改的时候就会自己添加置空进去吗?
使用TableField
注解确实可以达到你说的效果,但是得区分项目的整体情况;如果整个项目都是使用Mybatis-Plus
的LambdaUpdateWrapper
来对数据的状态或者价格等字段进行更新的话,那么这个注解的可行性就少了一部分。因为有些场景下你只需要对某个字段进行更新而不是更新所有字段,例如:我这是一张申请单据,对应人送审成功,我需要更新未处理
状态到处理中
,这种情况下我们是不需要更新状态字段之外的其他字段,如果使用这种方式则会把其他数据清空。除非在更新前查一遍数据,然后拿这份数据去修改,这样对程序和数据库来说增加压力!
/*** 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();// 将对象转换成MapMap<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();// 将对象转换成MapMap<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
工具看
@Resourceprivate 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.lang.UnsupportedOperationException: nullat 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]
🔍原因分析:
秉持出现问题,解决问题的思想 (哈哈哈
)。断点调试后,还是有问题;接下来就是看源码了,请展示:
@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);}@Overridepublic int size() {return a.length;}@Overridepublic 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;}@Overridepublic E get(int index) {return a[index];}@Overridepublic E set(int index, E element) {E oldValue = a[index];a[index] = element;return oldValue;}@Overridepublic 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;}@Overridepublic boolean contains(Object o) {return indexOf(o) != -1;}@Overridepublic Spliterator<E> spliterator() {return Spliterators.spliterator(a, Spliterator.ORDERED);}@Overridepublic void forEach(Consumer<? super E> action) {Objects.requireNonNull(action);for (E e : a) {action.accept(e);}}@Overridepublic 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]);}}@Overridepublic void sort(Comparator<? super E> c) {Arrays.sort(a, c);}}
可以从源码中看出,此ArrayList
非彼java.util.ArrayList
Arrays.asList()使用的是类部类中的ArrayList
;它继承的是AbstractList
,并没有实现AbstractList
里面的add()
方法!!!
看到这里相信各位同学应该知道问题了,没错就是没有实现里面的add()
所以导致了《空指针》
异常🌚
💡问题解决:
既然知道了问题,那么就好解决了!
换一个初始化List集合的工具就行啦!
我换成了hutool的CollectionUtil.toList
📝问题总结:
经验太少…哈哈哈哈
这篇关于Arrays.asList(T... a)导致的事故的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!