Java对象深拷贝 终极方案 deep clone an object

2 篇文章 0 订阅
1 篇文章 0 订阅

定义 深拷贝

  • 必须是全新的对象,object堆内存地址是全新的;
  • 对象的各个属性值 prop Value,也是全新的,指向全新的堆内存地址。
  • 作用: 原有旧对象和新对象,可以独立修改各自的属性值,互相之间没影响。
  • 场景:
    • java的method 参数传递的是内存地址,也就是对象的引用句柄,只有 primitive 基本类型和String 作为参数时,传递的才是值
    • 某个对象,需要作为 多个方法的参数,进行不同的操作。
      为了防止这个原始对象的属性值被意外修改,就需要 深拷贝为新对象。
      操作新对象,不会对 原始对象有任何影响。
    • 该对象作为参数进行传递的次数越多,因为属性的内存地址都是一样的,属性值被修改的风险就越高

深拷贝常见误区

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 🚗 . 速度最慢,但是兼容性较好
    if(javaObj instanceof String){
      return mapper.readValue(mapper.writeValueAsString(javaObj), targetClass);
    }

    // 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
深拷贝代码参考来源

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
Java中的对象拷贝有两种方式:浅拷贝和深拷贝。 浅拷贝是指创建一个新对象,新对象中的引用类型字段仍然指向原对象中对应字段的引用。这意味着修改新对象中的引用类型字段也会影响原对象。可以通过实现 Cloneable 接口并重写 clone() 方法来实现浅拷贝。 示例代码如下: ```java class MyClass implements Cloneable { private int value; private MyObject myObject; public MyClass(int value, MyObject myObject) { this.value = value; this.myObject = myObject; } // 重写 clone() 方法 @Override protected Object clone() throws CloneNotSupportedException { return super.clone(); } } class MyObject { // ... } public class Main { public static void main(String[] args) throws CloneNotSupportedException { MyObject obj = new MyObject(); MyClass obj1 = new MyClass(10, obj); // 浅拷贝 MyClass obj2 = (MyClass) obj1.clone(); // obj1 和 obj2 是两个独立的对象,但是它们的 myObject 字段引用同一个对象 System.out.println(obj1 == obj2); // false System.out.println(obj1.myObject == obj2.myObject); // true } } ``` 深拷贝是指创建一个新对象,同时递归地复制原对象及其引用类型字段所引用的对象。这样在修改新对象时不会影响原对象。可以通过实现 Serializable 接口并使用序列化/反序列化来实现深拷贝。 示例代码如下: ```java import java.io.*; class MyClass implements Serializable { private int value; private MyObject myObject; public MyClass(int value, MyObject myObject) { this.value = value; this.myObject = myObject; } // 深拷贝 public MyClass deepClone() throws IOException, ClassNotFoundException { ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream); objectOutputStream.writeObject(this); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream); return (MyClass) objectInputStream.readObject(); } } class MyObject implements Serializable { // ... } public class Main { public static void main(String[] args) throws IOException, ClassNotFoundException { MyObject obj = new MyObject(); MyClass obj1 = new MyClass(10, obj); // 深拷贝 MyClass obj2 = obj1.deepClone(); // obj1 和 obj2 是两个独立的对象,它们的 myObject 字段引用不同的对象 System.out.println(obj1 == obj2); // false System.out.println(obj1.myObject == obj2.myObject); // false } } ``` 上述代码中,通过使用序列化/反序列化实现了深拷贝。在 `deepClone()` 方法中,首先将对象写入字节数组输出流,然后通过字节数组输入流读取字节数组并反序列化为一个新的对象。这样就能够得到一个与原对象完全独立的新对象,包括其引用类型字段所引用的对象

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值