为什么要深度拷贝

写这篇博客的原因: 最近使用 hibernate 时,合并集合时由于错误使用浅层拷贝,导致当使用集合的 clear() 方法时,两个对象的集合都被清除,导致数据的丢失

什么是深度拷贝: 深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响

JDk中提供的常用的集合拷贝方法,如Collections.addAll() 、Collections.copy(des,src)、list.clone、new ArrayList(list)等都是浅层拷贝,因此使用时,可能会产生一些问题

样例:

public class TestDemo01 {
    public static void main(String[] args) throws Exception {
        List<User> src = new LinkedList<>();
        src.add(new User("123","123"));
        List<User> clone = new LinkedList<>(src);
        System.err.println("克隆对象修改前:: " + src);
        clone.get(0).setId("456");
        System.err.println("克隆对象修改后:: " + src);
    }
}

输出:

image

从上面的代码中可以看到,直接使用 new List() 构造方法来进行拷贝时,虽然两个集合的引用并不相同,但是其中的元素任然引用至同一个地址,当对一个集合中的对象进行操作时,另一个集合也会发生变化,这种情况下数据是不安全的,故而我们需要一种深度拷贝集合的方法

深度拷贝的实现

基于IO流实现

注意点: 克隆的对象要实现 Serializable 接口

	public static Object cloneObj(Object obj) throws IOException, ClassNotFoundException {
        if (obj == null)
            return null;
        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
        ObjectOutputStream outputStream = null;
        ObjectInputStream inputStream = null;
        try {
            outputStream = new ObjectOutputStream(byteOut);
            outputStream.writeObject(obj);
            inputStream = new ObjectInputStream(new ByteArrayInputStream(byteOut.toByteArray()));
            return inputStream.readObject();
        } finally {
            byteOut.close();
            if (outputStream != null) {
                outputStream.close();
            }
            if (inputStream != null) {
                inputStream.close();
            }
        }
    }

测试:

public class TestDemo01 {
    public static void main(String[] args) throws Exception {
        List<User> src = new LinkedList<>();
        src.add(new User("123","123"));
        List<User> clone = (List<User>) CollectionUtil.cloneObj( src);
        System.err.println("克隆对象修改前:: " + src);
        clone.get(0).setId("456");
        System.err.println("克隆对象修改后:: " + src);
    }
}

输出:

image-1654071731336

从上面的测试中可以看到,当使用深度拷贝的方法进行集合拷贝后,一个集合中的值发生修改时,并不会影响到另一个集合,能有效地提高数据的安全性

基于commons.lang工具包实现

Commons Lang这一组API主要是提供一些基础的、通用的操作和处理,如自动生成toString()的结果、自动实现hashCode()和equals()方法、数组操作、枚举、日期和时间的处理等等

通过 commons.lang 工具包提供的 SerializationUtils.clone(Serializable obj) 方法可以将集合深度拷贝,其原理与上面IO流方式一致,故拷贝的对象也必须实现 Serializable 接口

SerializationUtils.clone 源码:

    public static Object clone(Serializable object) {
        return deserialize(serialize(object));
    }
    
    public static Object deserialize(byte[] objectData) {
        if (objectData == null) {
            throw new IllegalArgumentException("The byte[] must not be null");
        } else {
            ByteArrayInputStream bais = new ByteArrayInputStream(objectData);
            return deserialize((InputStream)bais);
        }
    
    public static Object deserialize(InputStream inputStream) {
        if (inputStream == null) {
            throw new IllegalArgumentException("The InputStream must not be null");
        } else {
            ObjectInputStream in = null;

            Object var2;
            try {
                in = new ObjectInputStream(inputStream);
                var2 = in.readObject();
            } catch (ClassNotFoundException var12) {
                throw new SerializationException(var12);
            } catch (IOException var13) {
                throw new SerializationException(var13);
            } finally {
                try {
                    if (in != null) {
                        in.close();
                    }
                } catch (IOException var11) {
                }

            }

            return var2;
        }
    }

测试:

public class TestDemo01 {
    public static void main(String[] args) throws Exception {
        List<User> src = new LinkedList<>();
        src.add(new User("123","123"));
        List<User> clone = (List<User>) SerializationUtils.clone((Serializable) src);
        System.err.println("克隆对象修改前:: " + src);
        clone.get(0).setId("456");
        System.err.println("克隆对象修改后:: " + src);
    }
}

输出:

image-1654071731336