ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Java 对象拷贝,怎样避免空值覆盖和数据误改?

Java 对象拷贝,怎样避免空值覆盖和数据误改? Java 对象拷贝怎样避免空值覆盖和数据误改开篇把请求对象转成另一个对象看起来只是复制同名属性。但源字段是 null 时目标默认值可能被覆盖复制后再改 List还可能连原对象一起改。下面分别看这两种情况给出可控制的写法。环境JDK 21、Spring Framework 6。BeanConverter 是应用自有工具依赖 Spring 的属性解析及 Guava 等组件不是 Spring BeanUtils 的别名。坑点坑点一源字段没值目标默认值也没了例如目标对象的 name 默认是“匿名”源对象的 name 是 null。直接把同名属性复制过去未必会保留这个默认值。下面的演示模型供调用示例共用publicstaticclassUser{privateStringname匿名;privateListStringtagsnewArrayList();publicStringgetName(){returnname;}publicvoidsetName(Stringname){this.namename;}publicListStringgetTags(){returntags;}publicvoidsetTags(ListStringtags){this.tagstags;}}坑点二新建了对象里面的集合却没有复制新对象与新集合是两回事。属性直接赋值时两个对象可能持有同一个 List向副本中添加元素也会影响源对象。正确写法避坑一跳过 null保留新对象的默认值BeanConverter 的属性循环先取得源值然后执行这段判断// 规则1源对象属性值为null跳过if(sourceValuenull){continue;}目标对象由无参构造创建再复制属性publicstaticTTbean2Bean(Objectsource,ClassTtargetClass,PropertyConverterpropertyConverter){paramNotNull(source,source);paramNotNull(targetClass,targetClass);try{// 使用缓存的构造函数创建目标对象TtargetgetConstructor(targetClass).newInstance();returncopyProperty(source,target,propertyConverter);}catch(Throwablethrowable){throwwrapAsRuntimeException(throwable,bean2Bean error, source: %starget: %s,source.getClass().getSimpleName(),targetClass.getSimpleName());}}这意味着它保留的是新对象的初始化值不是读取数据库旧记录后替您做局部更新。这个公开入口也没有接收已有目标对象。调用对照UsersourcenewUser();source.setName(null);UserordinarynewUser();BeanUtils.copyProperties(source,ordinary);System.out.println(ordinary.getName());// nullUsercopyBeanConverter.bean2Bean(source,User.class);System.out.println(copy.getName());// 匿名System.out.println(copysource);// false如果业务需要用 null 清空某个字段就不能依赖这里的跳过规则。自定义转换器也处理不到这些 null因为代码在调用转换器之前已经 continue。避坑二需要独立集合时明确复制集合同名属性的后续处理顺序如下。自定义转换器先获得机会没有返回转换值时再检查类型并直接赋值// 获取源属性类型Class?sourcePropertyTypesourceValue.getClass();// 获取目标属性类型Class?targetPropertyTypegetPropertyType(targetClass,propertyName);// 尝试调用自定义转换器, 如果自定义转换器明确返回null将继续尝试内部转换if(propertyConverter!null){ObjectcustomValuepropertyConverter.convert(propertyName,sourceValue,targetPropertyType);if(customValue!null){setPropertyValue(target,targetClass,propertyName,customValue);continue;}}// 规则2源对象属性值不为null且类型兼容直接赋值if(isAssignable(targetPropertyType,sourcePropertyType)){setPropertyValue(target,targetClass,propertyName,sourceValue);continue;}// 规则3源对象属性值不为null但类型不兼容直接抛异常提示类型不一致无法转换。throwwrapAsRuntimeException(copyProperty error, 源对象: %s和目标对象: %s 的同名属性: %s类型不一致, 前者类型: %s, 后者类型: %s,sourceClass.getSimpleName(),targetClass.getSimpleName(),propertyName,sourcePropertyType.getSimpleName(),targetPropertyType.getSimpleName());直接赋值没有递归复制对象。用下面的调用就能看到共享引用UsersourcenewUser();source.getTags().add(Java);UsercopyBeanConverter.bean2Bean(source,User.class);System.out.println(copy.getTags()source.getTags());// truecopy.getTags().add(Redis);System.out.println(source.getTags());// [Java, Redis]只需要独立的 List 容器时可以通过 PropertyConverter 复制这一项UsersourcenewUser();source.getTags().add(Java);UsercopyBeanConverter.bean2Bean(source,User.class,(name,value,targetType)-{if(tags.equals(name)){returnnewArrayList((List?)value);}returnnull;});System.out.println(copy.getTags()source.getTags());// falsecopy.getTags().clear();System.out.println(source.getTags());// [Java]返回 null 的意思是“继续默认转换”不是“把目标字段设成 null”也不是“跳过这个字段”。这里复制的是容器List 中的元素仍共享。例子用的是不可变 String如果元素是可变的订单对象修改元素内部字段仍可能影响源数据需按实际模型继续复制。适用条件适合字段名相同、目标类型明确、希望保留默认值的对象转换。源值非空但类型不兼容时会报错不会自动把字符串变成数字。父类或接口能够接收的实现类型则可能直接赋值例如 ArrayList 赋给 List。模型应有可访问的 getter/setter 和无参构造。实际实现遇到找不到 setter 会直接返回因此只读属性不适合按“已经成功赋值”处理。复制结果仍需要业务校验不能直接充当数据库更新指令。总结先定 null 的含义再定引用是否要独立。跳过 null 可以保留新对象默认值要修改副本里的集合就显式复制需要独立的那一层。不要把“创建新对象”当成“完成深拷贝”。如果这篇文章对您有用欢迎关注。后续会继续拆解 Java 后端的实用代码讲清写法、原理和使用时要注意的细节。
返回列表