Java对象深拷贝 终极方案
- 定义 深拷贝
- 深拷贝常见误区
- `spring / apache commons ` 等工具类的 `BeanUtils.copy` 方法 ❌
- 正确做法: 上中下3策 ✔
- json 序列化 (用jackson,别用其他的gson/fastjson/json-lib 等,不解释)
- objectMapper 工具类初始化
- 1. 对象 <===> json字符串 互相转换 (下策)
- 2. 对象 <===> jsonNode 互相转换 (中策)
- 3. 对象 <===> TokenBuffer 互相转换 (上策)
定义 深拷贝
- 必须是全新的对象,
object堆内存地址是全新
的; - 该
对象的各个属性值 prop Value
,也是全新的,指向全新的堆内存地址。 - 作用: 原有旧对象和新对象,可以独立修改各自的属性值,互相之间没影响。
- 场景:
- java的
method 参数传递的是内存地址
,也就是对象的引用句柄,只有 primitive 基本类型和String 作为参数时,传递的才是值 - 某个对象,需要作为 多个方法的参数,进行不同的操作。
为了防止这个原始对象的属性值被意外修改,就需要 深拷贝为新对象。
操作新对象,不会对 原始对象有任何影响。 - 该对象作为参数进行传递的次数越多,因为属性的内存地址都是一样的,属性值被修改的风险就越高
- java的
深拷贝常见误区
spring / apache commons
等工具类的 BeanUtils.copy
方法 ❌
- 查看源码可知,使用的是 对象的get/set 方式实现的
- 对象虽然是新的,但是 属性值的内存地址是相同的
- 修改新对象的属性值,会同时影响原有对象的属性值
- 如果属性是 特殊类型比如map 或者 list 或者 嵌套对象属性,可能就不好使了
正确做法: 上中下3策 ✔
json 序列化 (用jackson,别用其他的gson/fastjson/json-lib 等,不解释)
类似 java 的对象序列化和反序列化过程( object Serialization & deserialization ),产生的是全新的对象
不同的是,java 对象序列化需要落地为磁盘文件,jackson 序列化则正常运行在jvm内存中。
Jackson 是个神奇的东东,共有3种方式可实现深拷贝,来源
objectMapper 工具类初始化
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.util.TokenBuffer;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.List;
@Slf4j
@Component
public class JacksonUtils {
@Autowired
ObjectMapper objectMapper;
private static JacksonUtils MAPPER;
/**
* 非 Controller 层使用 @Autowired 方式注入 spring 初始化好的全局单例 objectMapper
* 无需自己手动 new ObjectMapper();
*/
@PostConstruct
public void init(){
MAPPER = this; // JacksonUtils 类的单例 bean
MAPPER.objectMapper = this.objectMapper; // spring启动过程中,会暴露该 objectMapper 单例
}
}
1. 对象 <===> json字符串 互相转换 (下策)
2. 对象 <===> jsonNode 互相转换 (中策)
3. 对象 <===> TokenBuffer 互相转换 (上策)
/**
* 不同class类型拷贝
* @Param clazz 这个类,必须有1个默认的构造 方法,这是使用 Jackson 正反序列化必须的;
* 如果是Gson 进行正反序列化,则没有该 构造方法 的要求 :
* Gson 参考 https://www.baeldung.com/java-deep-copy#2-json-serialization-with-gson
*/
@SneakyThrows
public static <T> T deepClone(Object javaObj, Class<T> clazz) {
ObjectMapper mapper = MAPPER.objectMapper;
// method 1 : json string 🚗
// return mapper.readValue(mapper.writeValueAsString(javaObj), clazz);
// method 2 : jsonNode tree 🛫
// return mapper.treeToValue(mapper.valueToTree(javaObj), clazz);
// method 3 : token buffer 🚀
TokenBuffer tb = new TokenBuffer(mapper, false);
mapper.writeValue(tb, javaObj);
return mapper.readValue(tb.asParser(), clazz);
}
/**
* 相同class类型拷贝
*/
@SneakyThrows
public static <T> T deepClone(T source) {
return (T) deepClone(source, source.getClass());
}
初始化工具类,来源todo
深拷贝代码参考来源