Java List 深浅拷贝对比

内容分享2小时前发布
0 0 0

拷贝的核心差异,本质就是“是否复制堆内存中的真实对象”。


一、List浅层拷贝(浅拷贝):只拷贝容器和引用,不拷贝元素对象

1. 定义

浅拷贝会创建一个新的List容器对象,但新List中的元素只是原List元素的引用地址拷贝(栈内存的地址复制),堆内存中的元素对象本身并没有被复制。

简单说:新List和原List是两个不同的“篮子”,但里面装的是同一个“苹果”

2. 浅拷贝的常见实现方式(源码示例)

先定义一个自定义引用类型(由于基本类型的List拷贝无深浅之分,只有引用类型才能体现差异):

// 自定义用户类(引用类型)
class User {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Getter/Setter(方便修改和查看属性)
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }

    @Override
    public String toString() {
        return "User{name='" + name + "', age=" + age + "}";
    }
}

接下来演示3种常见的List浅拷贝方式,并验证效果:

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class ListCopyDemo {
    public static void main(String[] args) {
        // 1. 初始化原List
        List<User> originalList = new ArrayList<>();
        originalList.add(new User("张三", 20));
        originalList.add(new User("李四", 25));
        System.out.println("原List初始值:" + originalList);

        // 方式1:使用ArrayList的构造器(最常用)
        List<User> shallowCopy1 = new ArrayList<>(originalList);
        // 方式2:使用List的clone()方法(ArrayList重写了clone,本质也是浅拷贝)
        List<User> shallowCopy2 = (List<User>) ((ArrayList<User>) originalList).clone();
        // 方式3:使用Stream流拷贝
        List<User> shallowCopy3 = originalList.stream().collect(Collectors.toList());

        // 2. 修改浅拷贝List中的元素属性(以shallowCopy1为例)
        shallowCopy1.get(0).setName("张三-修改后");
        shallowCopy1.get(0).setAge(21);

        // 3. 打印验证:原List和所有浅拷贝List的元素都被修改了
        System.out.println("原List修改后:" + originalList);
        System.out.println("浅拷贝1修改后:" + shallowCopy1);
        System.out.println("浅拷贝2修改后:" + shallowCopy2);
        System.out.println("浅拷贝3修改后:" + shallowCopy3);
    }
}

3. 输出结果与核心解释

List初始值:[User{name='张三', age=20}, User{name='李四', age=25}]List修改后:[User{name='张三-修改后', age=21}, User{name='李四', age=25}]
浅拷贝1修改后:[User{name='张三-修改后', age=21}, User{name='李四', age=25}]
浅拷贝2修改后:[User{name='张三-修改后', age=21}, User{name='李四', age=25}]
浅拷贝3修改后:[User{name='张三-修改后', age=21}, User{name='李四', age=25}]

核心缘由

  • 新List(shallowCopy1/2/3)是新创建的容器(堆内存中独立的List对象),但List中的每个User元素只是复制了引用地址(列如原List中第一个User的地址是0x123,浅拷贝后新List的第一个元素地址也是0x123);
  • 当修改浅拷贝List中User的属性时,实际修改的是堆内存中0x123地址对应的User对象,因此原List和所有浅拷贝List都会看到这个变化。

二、List深层拷贝(深拷贝):拷贝容器+所有元素对象

1. 定义

深拷贝会创建一个新的List容器对象,同时递归地拷贝List中的每一个元素对象(在堆内存中创建全新的元素对象)。

简单说:新List和原List是不同的“篮子”,里面装的也是完全独立的“苹果”

2. 深拷贝的实现方式(源码示例)

深拷贝需要让元素类支持“自身拷贝”,常见有3种方式,以下演示最通用的序列化方式(不受元素类结构限制):

第一步:让User类实现序列化接口(Serializable):

import java.io.Serializable;

// 实现Serializable接口,支持序列化/反序列化
class User implements Serializable {
    private static final long serialVersionUID = 1L; // 序列化版本号(必须)
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Getter/Setter/toString 同上
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public int getAge() { return age; }
    public void setAge(int age) { this.age = age; }

    @Override
    public String toString() {
        return "User{name='" + name + "', age=" + age + "}";
    }
}

第二步:实现深拷贝工具方法,并验证:

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.List;

public class ListDeepCopyDemo {
    // 深拷贝工具方法:通过序列化+反序列化实现
    @SuppressWarnings("unchecked")
    public static <T extends Serializable> List<T> deepCopy(List<T> original) {
        try (
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
        ) {
            // 1. 将原List序列化(写入字节流)
            oos.writeObject(original);
            oos.flush();

            // 2. 反序列化字节流,生成全新的List和元素对象
            try (
                ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
                ObjectInputStream ois = new ObjectInputStream(bis);
            ) {
                return (List<T>) ois.readObject();
            }
        } catch (Exception e) {
            throw new RuntimeException("深拷贝失败", e);
        }
    }

    public static void main(String[] args) {
        // 1. 初始化原List
        List<User> originalList = new ArrayList<>();
        originalList.add(new User("张三", 20));
        originalList.add(new User("李四", 25));
        System.out.println("原List初始值:" + originalList);

        // 2. 执行深拷贝
        List<User> deepCopyList = deepCopy(originalList);

        // 3. 修改深拷贝List中的元素属性
        deepCopyList.get(0).setName("张三-修改后");
        deepCopyList.get(0).setAge(21);

        // 4. 打印验证:原List不变,深拷贝List独立变化
        System.out.println("原List修改后:" + originalList);
        System.out.println("深拷贝List修改后:" + deepCopyList);
    }
}

3. 输出结果与核心解释

List初始值:[User{name='张三', age=20}, User{name='李四', age=25}]List修改后:[User{name='张三', age=20}, User{name='李四', age=25}]
深拷贝List修改后:[User{name='张三-修改后', age=21}, User{name='李四', age=25}]

核心缘由

  • 序列化会将原List和其中的所有User对象转换成字节流,反序列化时会根据字节流重新创建全新的List对象和User对象(堆内存中独立的地址);
  • 修改深拷贝List中的User属性,只会影响新创建的User对象,原List中的User对象完全不受影响。

补充:深拷贝的其他实现方式

  1. 元素类实现Cloneable接口:让User重写clone()方法,然后遍历原List,逐个clone元素并添加到新List;
  2. 手动new对象赋值:遍历原List,手动new User并复制属性,添加到新List(适合简单类)。

三、浅拷贝vs深拷贝核心区别对比

维度

浅层拷贝

深层拷贝

容器对象

新创建(独立)

新创建(独立)

元素对象

引用复用(同一个堆对象)

全新创建(独立的堆对象)

修改元素属性的影响

原List和拷贝List都会受影响

仅拷贝List受影响,原List无变化

实现复杂度

简单(JDK自带方法直接用)

复杂(需元素类支持序列化/克隆)

内存开销

小(仅拷贝引用)

大(拷贝所有元素对象)

适用场景

只读场景、元素无需修改

需修改拷贝后元素,且不影响原数据


总结

  1. 核心本质:浅拷贝只复制List容器和元素的引用地址,深拷贝会复制容器+所有元素的真实对象
  2. 关键前提:只有List中的元素是引用类型时,深浅拷贝才有差异(基本类型List拷贝无区别);
  3. 使用选择:只读场景用浅拷贝(高效),需修改拷贝后数据且不影响原数据时用深拷贝(安全)。

通过以上由浅入深的讲解和源码示例,你可以清晰区分List深浅拷贝的差异,以及在实际开发中该如何选择合适的拷贝方式。

© 版权声明

相关文章

暂无评论

none
暂无评论...