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
  }
}
相关推荐
前端小张同学几秒前
餐饮小程序需要你们
java·前端·后端
王中阳Go20 分钟前
都2026年了,PHP还纠结转Go还是Java呢?安利一个无缝迁移的框架~
java·后端·go
老华带你飞25 分钟前
二手商城|基于springboot 二手商城系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·spring
Tadas-Gao29 分钟前
GraphQL:下一代API架构的设计哲学与实践创新
java·分布式·后端·微服务·架构·graphql
老华带你飞41 分钟前
酒店预约|基于springboot 酒店预约系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·spring
dob41 分钟前
为什么你的if-else越写越乱?聊聊状态机
后端
古城小栈44 分钟前
Spring Boot + 边缘 GenAI:智能座舱应用开发实战
java·spring boot·后端
开心就好20251 小时前
使用 Ipa Guard 应对 App Store 4.3 风险的一些实践
后端
想用offer打牌1 小时前
一站式了解数据库三大范式(库表设计基础)
数据库·后端·面试
2501_941982051 小时前
Go 进阶:发送文件/图片消息的流程与实现
开发语言·后端·golang