面试复盘:Java实现深拷贝与浅拷贝

面试复盘:Java实现深拷贝与浅拷贝

在最近的一次面试中,我被问到了一个经典的Java问题:"如何实现深拷贝和浅拷贝?如果一个对象中还嵌套了其他对象的实例,如何实现深拷贝?"这个问题看似简单,但深入探讨后发现涉及到对象克隆的底层原理和多种实现方式。以下是我的复盘和详细解答。

一、浅拷贝与深拷贝的区别

1. 浅拷贝 (Shallow Copy)

  • 定义:浅拷贝会创建一个新对象,新对象复制了原始对象的基本数据类型字段和引用类型字段的值。但对于引用类型字段,新对象和原始对象指向的是同一个内存地址。
  • 特点
    • 基本数据类型(如int、double)是独立的副本,修改不会影响原对象。
    • 引用类型(如对象、数组)只是复制了引用地址,修改会影响原对象。
  • 典型场景 :Java中Object.clone()默认实现的就是浅拷贝。

2. 深拷贝 (Deep Copy)

  • 定义:深拷贝不仅复制基本数据类型字段,还会递归地复制引用类型字段的所有内容,确保新对象与原对象完全独立,修改一方不会影响另一方。
  • 特点
    • 所有字段(包括嵌套对象)都是独立的副本。
    • 实现复杂,需要处理对象图中的每一层引用。
  • 典型场景:需要完全独立的对象副本时使用,比如在多线程环境或数据持久化中。

3. 举个例子

假设有一个Person类,里面嵌套了一个Address对象:

java 复制代码
class Address {
    String city;

    public Address(String city) {
        this.city = city;
    }
}

class Person {
    String name;
    Address address;

    public Person(String name, Address address) {
        this.name = name;
        this.address = address;
    }
}
  • 浅拷贝 :新Person对象的address字段和原对象指向同一个Address实例。
  • 深拷贝 :新Person对象的address字段是一个全新的Address实例,与原对象无关。

二、Java实现浅拷贝

1. 使用Object.clone()

Java中的Object类提供了clone()方法,默认实现是浅拷贝。要使用它,类需要实现Cloneable接口并重写clone()方法。

java 复制代码
class Address implements Cloneable {
    String city;

    public Address(String city) {
        this.city = city;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

class Person implements Cloneable {
    String name;
    Address address;

    public Person(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone(); // 浅拷贝
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        Address addr = new Address("Beijing");
        Person p1 = new Person("Alice", addr);
        Person p2 = (Person) p1.clone();

        System.out.println(p1.address == p2.address); // true,说明address是同一个对象
        p2.address.city = "Shanghai";
        System.out.println(p1.address.city); // Shanghai,修改p2影响了p1
    }
}

问题p2.addressp1.address指向同一个Address对象,修改一方会影响另一方。


三、Java实现深拷贝

深拷贝需要确保嵌套对象也被复制。以下是几种常见实现方式:

1. 重写clone()实现深拷贝

Person类的clone()方法中,手动克隆address字段。

java 复制代码
class Address implements Cloneable {
    String city;

    public Address(String city) {
        this.city = city;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

class Person implements Cloneable {
    String name;
    Address address;

    public Person(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person cloned = (Person) super.clone(); // 先浅拷贝
        cloned.address = (Address) address.clone(); // 再深拷贝嵌套对象
        return cloned;
    }

    public static void main(String[] args) throws CloneNotSupportedException {
        Address addr = new Address("Beijing");
        Person p1 = new Person("Alice", addr);
        Person p2 = (Person) p1.clone();

        System.out.println(p1.address == p2.address); // false,address是独立对象
        p2.address.city = "Shanghai";
        System.out.println(p1.address.city); // Beijing,未受影响
    }
}

优点 :简单直观,适合对象结构不复杂的情况。 缺点 :需要为每个嵌套类实现clone(),代码冗余且不灵活。

2. 使用序列化实现深拷贝

通过将对象序列化为字节流,再反序列化回来,可以实现深拷贝。这种方式无需手动克隆每个字段。

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

class Address implements Serializable {
    String city;

    public Address(String city) {
        this.city = city;
    }
}

class Person implements Serializable {
    String name;
    Address address;

    public Person(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    public Person deepCopy() throws IOException, ClassNotFoundException {
        // 序列化
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);

        // 反序列化
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return (Person) ois.readObject();
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Address addr = new Address("Beijing");
        Person p1 = new Person("Alice", addr);
        Person p2 = p1.deepCopy();

        System.out.println(p1.address == p2.address); // false
        p2.address.city = "Shanghai";
        System.out.println(p1.address.city); // Beijing
    }
}

优点

  • 通用性强,适用于复杂对象图。
  • 无需为每个嵌套类手动实现克隆逻辑。 缺点
  • 需要实现Serializable接口。
  • 性能开销较大(涉及IO操作)。
  • 如果对象包含不可序列化的字段,会抛异常。

3. 使用构造方法或手动复制

通过构造方法或setter手动复制所有字段,包括嵌套对象。

java 复制代码
class Address {
    String city;

    public Address(String city) {
        this.city = city;
    }

    public Address deepCopy() {
        return new Address(this.city);
    }
}

class Person {
    String name;
    Address address;

    public Person(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    public Person deepCopy() {
        return new Person(this.name, this.address.deepCopy());
    }

    public static void main(String[] args) {
        Address addr = new Address("Beijing");
        Person p1 = new Person("Alice", addr);
        Person p2 = p1.deepCopy();

        System.out.println(p1.address == p2.address); // false
        p2.address.city = "Shanghai";
        System.out.println(p1.address.city); // Beijing
    }
}

优点 :代码清晰,控制粒度高。 缺点:需要为每个类手动编写复制逻辑,维护成本高。

4. 使用第三方库(如Gson或Jackson)

通过JSON序列化和反序列化实现深拷贝。

java 复制代码
import com.google.gson.Gson;

class Address {
    String city;

    public Address(String city) {
        this.city = city;
    }
}

class Person {
    String name;
    Address address;

    public Person(String name, Address address) {
        this.name = name;
        this.address = address;
    }

    public Person deepCopy() {
        Gson gson = new Gson();
        String json = gson.toJson(this);
        return gson.fromJson(json, Person.class);
    }

    public static void main(String[] args) {
        Address addr = new Address("Beijing");
        Person p1 = new Person("Alice", addr);
        Person p2 = p1.deepCopy();

        System.out.println(p1.address == p2.address); // false
        p2.address.city = "Shanghai";
        System.out.println(p1.address.city); // Beijing
    }
}

优点 :简单易用,适合复杂对象。 缺点:依赖外部库,性能不如手动实现。


四、面试中的注意点

  1. 澄清需求:先问清楚面试官是否需要处理循环引用(如对象A引用B,B又引用A),因为序列化等方式可能抛出栈溢出异常。
  2. 复杂度分析
    • clone():O(n),n为对象字段数。
    • 序列化:O(n) + IO开销。
    • JSON:O(n) + 反射开销。
  3. 异常处理 :实现时要注意CloneNotSupportedException或IO异常。
  4. 扩展性:如果对象结构复杂,推荐序列化或JSON方式。

五、总结

浅拷贝适用于简单场景,而深拷贝是面试中的重点。实现深拷贝时:

  • 简单对象 :用clone()或手动复制。
  • 复杂对象:用序列化或JSON库。
相关推荐
知识分享小能手10 分钟前
CSS3学习教程,从入门到精通,CSS3 浮动与清除浮动语法知识点及案例代码(14)
前端·css·后端·学习·html·css3·html5
Answer_ism6 小时前
【SpringMVC】SpringMVC拦截器,统一异常处理,文件上传与下载
java·开发语言·后端·spring·tomcat
盖世英雄酱581369 小时前
JDK24 它来了,抗量子加密
java·后端
Asthenia04129 小时前
无感刷新的秘密:Access Token 和 Refresh Token 的那些事儿
前端·后端
Asthenia041210 小时前
面试复盘:聊聊epoll的原理、以及其相较select和poll的优势
后端
luckyext10 小时前
SQLServer列转行操作及union all用法
运维·数据库·后端·sql·sqlserver·运维开发·mssql
Asthenia041210 小时前
ES:倒排索引的原理与写入分析
后端
圈圈编码11 小时前
Spring常用注解汇总
java·后端·spring
stark张宇11 小时前
PHP多版本共存终极填坑指南:一台服务器部署多实例的最佳实践
后端·php
Lian_Aseubel12 小时前
Springboot整合Netty简单实现1对1聊天(vx小程序服务端)
java·spring boot·后端