Java | 深拷贝与浅拷贝工具类解析和自定义实现

关注:CodingTechWork

引言

在 Java 开发中,对象的拷贝是一个常见的需求,尤其是在处理复杂数据结构时。深拷贝(Deep Copy)和浅拷贝(Shallow Copy)是两种常见的拷贝方式,它们在实现和效果上有着显著的区别。本文将详细介绍深拷贝和浅拷贝的概念、区别,并通过 Java 代码示例进行说明。同时,还会介绍一些常用的深拷贝工具类以及如何自己实现一个深拷贝工具。

深拷贝与浅拷贝的概念

浅拷贝

浅拷贝是指创建一个新对象,然后将当前对象的非静态字段复制到新对象中。如果字段是值类型的(如基本数据类型或不可变对象),那么将复制字段的值;如果字段是引用类型的(如数组、集合、自定义对象等),则复制引用但不复制引用的对象。因此,原始对象和副本对象将引用同一个对象。

深拷贝

深拷贝是指创建一个新对象,然后递归地将当前对象的所有字段(包括值类型和引用类型)复制到新对象中。对于引用类型的字段,深拷贝会创建一个新的对象,并将其复制到副本对象中。因此,原始对象和副本对象是完全独立的,修改一个对象不会影响另一个对象。

深拷贝与浅拷贝的区别

特性 浅拷贝 深拷贝
值类型字段 复制字段的值 复制字段的值
引用类型字段 复制引用,不复制引用的对象 创建新的对象,并复制引用的对象
修改影响 修改原始对象可能影响副本对象 修改原始对象不会影响副本对象
实现复杂度 简单 复杂,需要递归
性能 较快 较慢,尤其是对象结构复杂时

浅拷贝与深拷贝的代码示例

浅拷贝示例

java 复制代码
import java.util.Arrays;

class Person {
    private String name;
    private String[] address;

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

    public String getName() {
        return name;
    }

    public String[] getAddress() {
        return address;
    }

    // 浅拷贝方法
    public Person shallowCopy() {
        return new Person(this.name, this.address);
    }
}

public class ShallowCopyDemo {
    public static void main(String[] args) {
        // 创建原始对象
        Person original = new Person("Alice", new String[]{"123 Main St", "Apt 456"});

        // 浅拷贝
        Person shallowCopied = original.shallowCopy();

        // 修改原始对象的地址
        original.getAddress()[0] = "456 Elm St";

        System.out.println("Original Address: " + Arrays.toString(original.getAddress()));
        System.out.println("Shallow Copied Address: " + Arrays.toString(shallowCopied.getAddress()));
    }
}

输出:

less 复制代码
Original Address: [456 Elm St, Apt 456]
Shallow Copied Address: [456 Elm St, Apt 456]

从输出可以看到,修改原始对象的地址也影响了浅拷贝的对象,因为它们共享同一个地址数组。

深拷贝示例

java 复制代码
import java.util.Arrays;

class Person {
    private String name;
    private String[] address;

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

    public String getName() {
        return name;
    }

    public String[] getAddress() {
        return address;
    }

    // 深拷贝方法
    public Person deepCopy() {
        // 创建新的地址数组
        String[] newAddress = new String[this.address.length];
        System.arraycopy(this.address, 0, newAddress, 0, this.address.length);

        return new Person(this.name, newAddress);
    }
}

public class DeepCopyDemo {
    public static void main(String[] args) {
        // 创建原始对象
        Person original = new Person("Alice", new String[]{"123 Main St", "Apt 456"});

        // 深拷贝
        Person deepCopied = original.deepCopy();

        // 修改原始对象的地址
        original.getAddress()[0] = "456 Elm St";

        System.out.println("Original Address: " + Arrays.toString(original.getAddress()));
        System.out.println("Deep Copied Address: " + Arrays.toString(deepCopied.getAddress()));
    }
}

输出:

less 复制代码
Original Address: [456 Elm St, Apt 456]
Deep Copied Address: [123 Main St, Apt 456]

从输出可以看到,修改原始对象的地址不会影响深拷贝的对象,因为深拷贝创建了一个全新的地址数组。

深拷贝的常用工具类

在 Java 中,有一些常用的工具类可以帮助实现深拷贝,例如 SerializationUtilsJSON 序列化。

使用 SerializationUtils 实现深拷贝

SerializationUtils 是 Apache Commons Lang 提供的工具类,可以利用序列化机制实现深拷贝。以下是示例代码:

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

import java.io.Serializable;

class Person implements Serializable {
    private String name;
    private String[] address;

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

    public String getName() {
        return name;
    }

    public String[] getAddress() {
        return address;
    }
}

public class SerializationUtilsDemo {
    public static void main(String[] args) {
        // 创建原始对象
        Person original = new Person("Alice", new String[]{"123 Main St", "Apt 456"});

        // 使用 SerializationUtils 实现深拷贝
        Person deepCopied = SerializationUtils.clone(original);

        // 修改原始对象的地址
        original.getAddress()[0] = "456 Elm St";

        System.out.println("Original Address: " + Arrays.toString(original.getAddress()));
        System.out.println("Deep Copied Address: " + Arrays.toString(deepCopied.getAddress()));
    }
}

输出:

less 复制代码
Original Address: [456 Elm St, Apt 456]
Deep Copied Address: [123 Main St, Apt 456]

使用 JSON 序列化实现深拷贝

JSON 序列化是一种常见的深拷贝方式,可以利用 GsonJackson 等库实现。以下是使用 Gson 的示例代码:

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

class Person {
    private String name;
    private String[] address;

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

    public String getName() {
        return name;
    }

    public String[] getAddress() {
        return address;
    }
}

public class GsonDemo {
    public static void main(String[] args) {
        // 创建原始对象
        Person original = new Person("Alice", new String[]{"123 Main St", "Apt 456"});

        // 使用 Gson 实现深拷贝
        Gson gson = new Gson();
        String json = gson.toJson(original);
        Person deepCopied = gson.fromJson(json, Person.class);

        // 修改原始对象的地址
        original.getAddress()[0] = "456 Elm St";

        System.out.println("Original Address: " + Arrays.toString(original.getAddress()));
        System.out.println("Deep Copied Address: " + Arrays.toString(deepCopied.getAddress()));
    }
}

输出:

less 复制代码
Original Address: [456 Elm St, Apt 456]
Deep Copied Address: [123 Main St, Apt 456]

自己实现深拷贝工具类

如果需要更灵活的深拷贝实现,可以自己编写一个工具类。以下是一个简单的深拷贝工具类的实现:

java 复制代码
import java.util.ArrayList;
import java.util.List;

public class DeepCopyUtils {

    public static <T> T deepCopy(T object) {
        if (object == null) {
            return null;
        }

        // 检查是否是基本数据类型或不可变对象
        if (object instanceof String || object instanceof Number || object instanceof Boolean) {
            return object;
        }

        // 检查是否是数组
        if (object.getClass().isArray()) {
            Object[] array = (Object[]) object;
            Object[] newArray = new Object[array.length];
            for (int i = 0; i < array.length; i++) {
                newArray[i] = deepCopy(array[i]);
            }
            return (T) newArray;
        }

        // 检查是否是集合
        if (object instanceof List) {
            List<Object> list = (List<Object>) object;
            List<Object> newList = new ArrayList<>();
            for (Object item : list) {
                newList.add(deepCopy(item));
            }
            return (T) newList;
        }

        // 如果是自定义对象,需要手动处理
        if (object instanceof Person) {
            Person person = (Person) object;
            return (T) new Person(person.getName(), deepCopy(person.getAddress()));
        }

        throw new IllegalArgumentException("Unsupported type: " + object.getClass().getName());
    }

    private static String[] deepCopy(String[] array) {
        if (array == null) {
            return null;
        }
        String[] newArray = new String[array.length];
        System.arraycopy(array, 0, newArray, 0, array.length);
        return newArray;
    }

    public static void main(String[] args) {
        // 创建原始对象
        Person original = new Person("Alice", new String[]{"123 Main St", "Apt 456"});

        // 使用自定义工具类实现深拷贝
        Person deepCopied = DeepCopyUtils.deepCopy(original);

        // 修改原始对象的地址
        original.getAddress()[0] = "456 Elm St";

        System.out.println("Original Address: " + Arrays.toString(original.getAddress()));
        System.out.println("Deep Copied Address: " + Arrays.toString(deepCopied.getAddress()));
    }
}

输出:

less 复制代码
Original Address: [456 Elm St, Apt 456]
Deep Copied Address: [123 Main St, Apt 456]

总结

概念

  • 浅拷贝:只复制对象的直接字段,对于引用类型字段,复制的是引用而不是对象本身。
  • 深拷贝:递归复制对象的所有字段,包括引用类型字段所指向的对象。

区别

  • 浅拷贝创建的对象与原始对象可能共享引用类型字段所指向的对象,而深拷贝创建的对象与原始对象完全独立。
  • 深拷贝的实现更复杂,性能开销更大,但可以保证对象的完全独立性。

实现方式

  • 浅拷贝 :可以通过 Object.clone() 方法或手动实现。
  • 深拷贝 :可以通过手动递归实现、利用序列化机制(如 SerializationUtils)、JSON 序列化(如 GsonJackson)或自定义工具类实现。

选择建议

  • 如果对象结构简单,且不需要完全独立的副本,可以选择浅拷贝。
  • 如果需要完全独立的副本,尤其是对象结构复杂时,建议使用深拷贝。
  • 在实际开发中,可以根据具体需求选择合适的实现方式,或者结合多种方式实现更灵活的拷贝逻辑。
相关推荐
热爱学习的路人甲3 分钟前
GoKV
后端
陈哥聊测试4 分钟前
开发认为测试不及时,测试吐槽工作量太大?
后端·测试·devops
写bug写bug5 分钟前
为什么 LIMIT 0, 10 快,而 LIMIT 1000000, 10 慢?
数据库·后端·mysql
飞鱼荷兰猪23 分钟前
LLM大语言模型简述
后端·aigc
小爷毛毛_卓寿杰29 分钟前
【Dify(v1.x) 核心源码深入解析】errors、extension 和 external_data_tool 模块
人工智能·后端·python
溪饱鱼36 分钟前
秒杀传统数据库!Cloudflare D1 + Drizzle组合拳,高并发高可用,让我们的成本爆降10倍 - D1
前端·后端
廖广杰42 分钟前
java虚拟机-为什么TLAB能提升对象分配效率?如何配置TLAB大小
后端
终身学习基地2 小时前
第一篇:Django简介
后端·python·django
Apifox3 小时前
Apifox 4月更新|Apifox在线文档支持LLMs.txt、评论支持使用@提及成员、支持为团队配置「IP 允许访问名单」
前端·后端·ai编程
我家领养了个白胖胖3 小时前
#和$符号使用场景 注意事项
java·后端·mybatis