java中实现对象深克隆的四种方式

在Java中实现对象的深克隆(Deep Clone)需要确保对象及其所有引用类型的字段都被复制,而不是共享相同的内存地址。以下是几种常见的实现方式:

方法1:手动实现深克隆

通过重写 clone() 方法并逐层复制引用对象:

java 复制代码
@Data
@AllArgsConstructor
@NoArgsConstructor
class Address implements Cloneable {
    private String city;
    
    @Override
    protected Address clone() throws CloneNotSupportedException {
        return (Address) super.clone();
    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class Person implements Cloneable {
    private String name;
    private Address address;
    private List<String> hobbies;

    @Override
    protected Person clone() throws CloneNotSupportedException {
        Person cloned = (Person) super.clone();
        // 深克隆引用对象
        cloned.address = this.address.clone();
        // 若hobbies是自定义对象列表,需逐个深克隆
        return cloned;
    }
}

public class DeepCloneExample {
    public static void main(String[] args) throws CloneNotSupportedException {
        Address addr = new Address("Beijing");
        List<String> hobbies = List.of("reading", "swimming");
        Person p1 = new Person("Alice", addr, hobbies);
        
        // 深克隆
        Person p2 = p1.clone();
        
        // 修改克隆对象的属性,不会影响原对象
        p2.getAddress().setCity("Shanghai");
        p2.getHobbies().add("running");
        
        System.out.println(p1.getAddress().getCity()); // 输出: Beijing
        System.out.println(p2.getAddress().getCity()); // 输出: Shanghai
        System.out.println(p1.getHobbies()); // 输出: [reading, swimming]
        System.out.println(p2.getHobbies()); // 输出: [reading, swimming, running]
    }
}

关键点:

  1. 实现 Cloneable 接口:标记类支持克隆。
  2. 重写 clone() 方法 :调用 super.clone() 后,手动复制引用类型字段。
  3. 逐层深克隆 :对每个引用对象(如 Address)也需实现深克隆。

方法2:使用序列化 Serializable

通过序列化和反序列化实现深克隆:

java 复制代码
@Data
@AllArgsConstructor
@NoArgsConstructor
class Address implements Serializable {
    private static final long serialVersionUID = 1L;
    private String city;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private Address address;
    private List<String> hobbies;
    
    // 深克隆方法
    public Person deepClone() {
      // 序列化
      try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt"))) {
          oos.writeObject(this);
      } catch (Exception e) {
          e.printStackTrace();
      }

      // 反序列化
      try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("person.txt"))) {
          return (Person) ois.readObject();
      } catch (Exception e) {
          e.printStackTrace();
      }
        
    }
}

public class DeepCloneBySerialization {
    public static void main(String[] args) {
        Address addr = new Address("Beijing");
        List<String> hobbies = new ArrayList<>(List.of("reading", "swimming"));
        Person p1 = new Person("Alice", addr, hobbies);
        
        // 深克隆
        Person p2 = p1.deepClone();
        
        // 修改克隆对象的属性,不会影响原对象
        p2.getAddress().setCity("Shanghai");
        p2.getHobbies().add("running");
        
        System.out.println(p1.getAddress().getCity()); // 输出: Beijing
        System.out.println(p2.getAddress().getCity()); // 输出: Shanghai
        System.out.println(p1.getHobbies()); // 输出: [reading, swimming]
        System.out.println(p2.getHobbies()); // 输出: [reading, swimming, running]
    }
}

使用 ByteArrayOutputStream 将对象序列化为内存中的字节数组。 再通过 ByteArrayInputStream 从内存中读取该字节数组并反序列化成新对象。 整个过程不涉及任何磁盘 I/O,完全在内存中完成,效率更高。

java 复制代码
import java.io.*;

public class DeepCloneUtil {
    public static <T extends Serializable> T deepClone(T object) {
        try (
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
        ) {
            // 序列化对象到内存中的字节数组
            oos.writeObject(object);

            try (
                ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
                ObjectInputStream ois = new ObjectInputStream(bis);
            ) {
                // 反序列化并返回新对象
                return (T) ois.readObject();
            }
        } catch (IOException | ClassNotFoundException e) {
            throw new RuntimeException("深克隆失败", e);
        }
    }

    public static void main(String[] args) {
        Address address = new Address("Beijing");
        Person person1 = new Person("Alice", address, List.of("reading", "swimming"));

        // 深克隆
        Person person2 = DeepCloneUtil.deepClone(person1);

        // 修改克隆对象的属性,不会影响原对象
        person2.getAddress().setCity("Shanghai");

        System.out.println(person1.getAddress().getCity()); // 输出: Beijing
        System.out.println(person2.getAddress().getCity()); // 输出: Shanghai
    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class Address implements Serializable {
    private static final long serialVersionUID = 1L;
    private String city;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class Person implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private Address address;
    private List<String> hobbies;
}

关键点:

  1. 实现 Serializable 接口:所有类(包括嵌套类)必须可序列化。
  2. 通过流操作:将对象序列化为字节流,再反序列化为新对象,确保所有引用都被复制。
  3. 自动处理嵌套对象:无需手动逐层克隆

方法3:使用第三方库 Apache Commons Lang(推荐使用,因为一般项目中都会引入这个依赖)

xml 复制代码
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
</dependency>

使用 SerializationUtils.clone()

java 复制代码
import org.apache.commons.lang3.SerializationUtils;

// 确保所有类实现Serializable接口
class Address implements Serializable { /* ... */ }
class Person implements Serializable { /* ... */ }

public class DeepCloneWithLibrary {
    public static void main(String[] args) {
        // 创建对象
        Person p1 = new Person("Alice", new Address("Beijing"), List.of("reading"));
        
        // 深克隆
        Person p2 = SerializationUtils.clone(p1);
        
        // 修改克隆对象,不影响原对象
        p2.getAddress().setCity("Shanghai");
    }
}

方法4:使用第三方库 Hutool

使用 CloneUtil.cloneByStream(obj)

需要对象及其嵌套对象实现 java.io.Serializable 接口

java 复制代码
@Data
@AllArgsConstructor
@NoArgsConstructor
class Address implements java.io.Serializable {
  private String city;
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class Person implements java.io.Serializable {
  private String name;
  private Address address;
}

public class HutoolDeepCloneExample {
  public static void main(String[] args) {
    Address addr = new Address("Beijing");
    Person p1 = new Person("Alice", addr);
    
    Person p2 = CloneUtil.cloneByStream(p1);
    
    p2.getAddress().setCity("Shanghai"); // 修改克隆对象的属性,不会影响原对象
    System.out.println(p1.getAddress().getCity()); // 输出: Beijing
    System.out.println(p2.getAddress().getCity()); // 输出: Shanghai
  }
}
相关推荐
野犬寒鸦12 小时前
从零起步学习并发编程 || 第一章:初步认识进程与线程
java·服务器·后端·学习
我爱娃哈哈12 小时前
SpringBoot + Flowable + 自定义节点:可视化工作流引擎,支持请假、报销、审批全场景
java·spring boot·后端
李梨同学丶14 小时前
0201好虫子周刊
后端
思想在飞肢体在追14 小时前
Springboot项目配置Nacos
java·spring boot·后端·nacos
Loo国昌16 小时前
【垂类模型数据工程】第四阶段:高性能 Embedding 实战:从双编码器架构到 InfoNCE 损失函数详解
人工智能·后端·深度学习·自然语言处理·架构·transformer·embedding
ONE_PUNCH_Ge17 小时前
Go 语言泛型
开发语言·后端·golang
良许Linux17 小时前
DSP的选型和应用
后端·stm32·单片机·程序员·嵌入式
不光头强17 小时前
spring boot项目欢迎页设置方式
java·spring boot·后端
怪兽毕设18 小时前
基于SpringBoot的选课调查系统
java·vue.js·spring boot·后端·node.js·选课调查系统
学IT的周星星18 小时前
Spring Boot Web 开发实战:第二天,从零搭个“会卖萌”的小项目
spring boot·后端·tomcat