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
  }
}
相关推荐
噢,我明白了6 小时前
java中唯一键和幂等键的应用
java·后端
GreenTea6 小时前
我把 Agent 的 while 循环拆掉了:一种你可能没想到的 Agent 架构
前端·后端·架构
考虑考虑6 小时前
synchronized字符串常量
java·后端·java ee
颜进强7 小时前
16 · NestJS LifecycleEvents 生命周期事件:五个钩子、三条边界,和 rag-server 的优雅停机
前端·后端·ai编程
GetcharZp7 小时前
定位不再一个个吐坐标:英伟达 LocateAnything 上手全攻略
后端
郑州光合科技余经理9 小时前
同城电商系统:库存变更怎么同步到订单
java·开发语言·前端·后端·uni-app·php·ai编程
我叫黑大帅9 小时前
Go日志库工程选型与逃逸分析评测报告
后端·面试·go
IT_陈寒11 小时前
JavaScript闭包的这个坑,我居然今天才爬出来
前端·人工智能·后端
Thneonl11 小时前
消息队列选型决策树:RabbitMQ vs Kafka vs Redis Streams
后端·架构