浅拷贝 VS 深拷贝

128 阅读3分钟

前言

在我们项目开发过程中,对象复制是我们必不可少的关键操作,浅拷贝和深拷贝就是我们选择使用进行对象复制的。之前的自己,对这2个有所了解,也能说出个七七八八,但是还是决定写这篇文章来聊一聊它俩,其一是加深印象,其二是在温故知新。

浅拷贝(Shallow Copy)

只复制对象的最外层数据,对于对象内部的引用类型(String除外),仅复制引用地址,而不是复制对象内部的引用类型数据。结果就是源对象和复制对象共享相同的一个内部对象实例,若内部对象被修改,也会影响另一个对象。 下面是浅拷贝的简单实现例子:

public class User implements Cloneable {
    private int id;

    private String name;

    private List<String> hobby;

    public User() {
    }

    public User(int id, String name, List<String> hobby) {
        this.id = id;
        this.name = name;
        this.hobby = hobby;
    }

    /**
     * 默认浅拷贝
     *
     * @return 对象
     * @throws CloneNotSupportedException
     */
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<String> getHobby() {
        return hobby;
    }

    public void setHobby(List<String> hobby) {
        this.hobby = hobby;
    }
    
    public static void main(String[] args) throws CloneNotSupportedException {
        List<String> hobby = new ArrayList<>();
        hobby.add("看电影");
        String name = "linyi";
        User user_1 = new User(1, name, hobby);

        //浅拷贝一个User对象
        User user_2 = (User) user_1.clone();

        //修改user_2的name
        name = "ly";
        user_2.setName(name);
        //打印user_1和user_2的name
        System.out.println("user_1:" + user_1.getName());
        System.out.println("user_2:" + user_2.getName());

        //修改user_2的hobby
        hobby.add("看动漫");
        System.out.println("user_1:" + user_1.getHobby());
        System.out.println("user_2:" + user_2.getHobby());
    }

}

image.png

为什么String也是引用类型,却没有修改呢?因为String它是不可变的对象呀!每次对String的修改就会重新创建一个新的String对象,而原始的String对象没有发生改变。

image.png

浅拷贝草图:

image.png

深拷贝(Deep Copy)

对对象进行递归复制,就是不仅复制最外层数据,还将所有的引用类型的对象进行复制,形成一份完全对立的对象副本。拷贝对象和源对象之间互不影响,修改任何一个对象都不会影响另一个对象。

public class User implements Serializable {
    private int id;

    private String name;

    private List<String> hobby;

    public User() {
    }

    public User(int id, String name, List<String> hobby) {
        this.id = id;
        this.name = name;
        this.hobby = hobby;
    }

    /**
     * 深拷贝方法
     *
     * @return 深拷贝后的 User 对象
     */
    protected User deepCopy() throws Exception {
        // 使用 try-with-resources 确保资源正确关闭
        try (ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
             ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream)) {
    
            objectOutputStream.writeObject(this); // 将当前对象写入字节流
    
            // 使用 try-with-resources 确保资源正确关闭
            try (ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
                 ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream)) {
    
                return (User) objectInputStream.readObject(); // 从字节流读取并返回深拷贝对象
            }
        }
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<String> getHobby() {
        return hobby;
    }

    public void setHobby(List<String> hobby) {
        this.hobby = hobby;
    }

    public static void main(String[] args) throws Exception {
        //创建一个User对象
        List<String> hobby = new ArrayList<>();

        hobby.add("看电影");
        String name = "linyi";
        User user_1 = new User(1, name, hobby);

        //浅拷贝一个User对象
        User user_2 = user_1.deepCopy();
        System.out.println("user_1:"+ "hobby的哈希值"+System.identityHashCode(user_1.getHobby())+"--hobby:"+ user_1.getHobby());
        System.out.println("user_2:"+"hobby的哈希值"+ System.identityHashCode(user_2.getHobby())+"--hobby:"+ user_2.getHobby());
    }

}

运行结果:

image.png

哈希值不通,不是一个对象。 深拷贝草图:

image.png

流程图

graph TD
A[原对象] --> B[基本类型字段]

A --> C[引用类型字段]

C --> D[堆内存对象X]

subgraph 浅拷贝

E[拷贝对象] --> F[基本类型拷贝值]

E --> G[引用类型复制地址]

G --> D

end

subgraph 深拷贝

H[拷贝对象] --> I[基本类型拷贝值]

H --> J[引用类型创建新对象]

J --> K[堆内存新对象Y]

end

浅拷贝VS深拷贝

浅拷贝深拷贝
基本数据类型值拷贝值拷贝
引用数据类型(String除外)复制内存地址(与源对象同一地址)递归创建新对象(一个新地址)
内存消耗
修改影响修改引用对象,源对象会改变修改引用对象,源对象不会改变
复杂程度
典型实现方式Object.clone()默认实现递归实现、序列化和反序列化
适用场景高性能拷贝操作、需共享引用对象需要完全隔离拷贝前后对象、涉及线程安全的数据隔离