Java经典面试题(21): 为什么要使用克隆?如何实现对象克隆?深拷贝和浅拷贝区别是什么?

Java经典面试题(21): 为什么要使用克隆?如何实现对象克隆?深拷贝和浅拷贝区别是什么?
最新回答
其实我过得并不好

2026-04-18 09:43:47

使用克隆的原因、实现方式及深拷贝与浅拷贝的区别如下:

一、为什么要使用克隆

当需要对一个对象进行复制,同时保留原有对象以进行后续操作时,克隆是必要的。例如:

  • 避免直接引用传递:若直接传递对象引用,修改副本会影响原对象。
  • 保持对象独立性:克隆后的对象与原对象在内存中独立存储,修改互不影响。
  • 简化对象创建:对于复杂对象,克隆比手动创建更高效,尤其是需要复制大量属性时。
二、如何实现对象克隆

实现克隆主要有以下三种方式:

  1. 通过Cloneable接口和clone()方法

    步骤:

    实现Cloneable接口(标记接口,无方法)。

    重写Object.clone()方法,将其访问权限改为public。

    在clone()方法中调用super.clone()(浅拷贝)或手动实现深拷贝。

    示例代码:

    class MyClass implements Cloneable { private int value; private List<String> list; @Override public Object clone() throws CloneNotSupportedException { MyClass cloned = (MyClass) super.clone(); // 浅拷贝 // 深拷贝需手动复制引用类型字段 cloned.list = new ArrayList<>(this.list); return cloned; }}
  2. 通过序列化实现深拷贝

    步骤:

    实现Serializable接口。

    将对象序列化为字节流,再反序列化为新对象。

    优点:自动处理引用类型,实现真正的深拷贝。

    缺点:性能较低,需处理序列化异常。

    示例代码:

    import java.io.*;class DeepCopyUtil { public static <T extends Serializable> T deepCopy(T object) { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(object); ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); return (T) ois.readObject(); } catch (IOException | ClassNotFoundException e) { throw new RuntimeException("Deep copy failed", e); } }}
  3. 使用工具类(如Apache Commons BeanUtils)

    注意:工具类(如BeanUtils.copyProperties())通常实现浅拷贝。

    示例:

    import org.apache.commons.beanutils.BeanUtils;class Source { private String name; private List<String> items; // getters/setters}class Target { private String name; private List<String> items; // getters/setters}Source source = new Source();source.setName("Test");source.setItems(Arrays.asList("A", "B"));Target target = new Target();try { BeanUtils.copyProperties(target, source); // 浅拷贝} catch (Exception e) { e.printStackTrace();}
三、深拷贝与浅拷贝的区别
  • 浅拷贝(Shallow Copy)

    定义:仅复制基本类型字段和引用类型字段的引用(内存地址),不复制引用指向的对象。

    特点:

    新对象与原对象共享引用类型字段,修改其中一个会影响另一个。

    实现简单(如直接调用super.clone()或工具类)。

    适用场景:对象无嵌套引用或无需独立修改引用类型字段时。

  • 深拷贝(Deep Copy)

    定义:复制基本类型字段和所有引用类型字段指向的对象(递归复制)。

    特点:

    新对象与原对象完全独立,修改互不影响。

    实现复杂(需手动复制嵌套对象或通过序列化)。

    适用场景:对象包含嵌套引用且需完全独立时。

  • 对比总结

    内存结构:浅拷贝中引用类型字段共享同一对象;深拷贝中引用类型字段指向新对象。

    性能:浅拷贝更快,深拷贝因递归复制可能更耗时。

    安全性:深拷贝更安全,避免意外修改共享数据。

四、代码实例对比// 浅拷贝示例class ShallowCopyExample { public static void main(String[] args) throws CloneNotSupportedException { class Person implements Cloneable { String name; List<String> hobbies; @Override public Person clone() { try { return (Person) super.clone(); // 浅拷贝 } catch (CloneNotSupportedException e) { throw new AssertionError(); } } } Person p1 = new Person(); p1.name = "Alice"; p1.hobbies = new ArrayList<>(Arrays.asList("Reading", "Swimming")); Person p2 = p1.clone(); p2.hobbies.add("Dancing"); // 修改p2的hobbies会影响p1 System.out.println(p1.hobbies); // 输出 [Reading, Swimming, Dancing] }}// 深拷贝示例(序列化实现)class DeepCopyExample { public static void main(String[] args) { class Person implements Serializable { String name; List<String> hobbies; } Person p1 = new Person(); p1.name = "Bob"; p1.hobbies = new ArrayList<>(Arrays.asList("Music", "Travel")); // 序列化深拷贝 try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos)) { oos.writeObject(p1); try (ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais)) { Person p2 = (Person) ois.readObject(); p2.hobbies.add("Gaming"); // 修改p2的hobbies不影响p1 System.out.println(p1.hobbies); // 输出 [Music, Travel] System.out.println(p2.hobbies); // 输出 [Music, Travel, Gaming] } } catch (IOException | ClassNotFoundException e) { e.printStackTrace(); } }}五、总结
  • 克隆的核心目的:创建独立对象副本,避免直接引用传递导致的数据污染。
  • 实现选择:

    简单场景用Cloneable接口(需注意浅拷贝问题)。

    复杂嵌套对象用序列化或手动深拷贝。

    快速开发可用工具类(但需确认是否为深拷贝)。

  • 拷贝类型选择:

    无需独立修改引用类型时用浅拷贝。

    需完全隔离数据时用深拷贝。