当前位置:   article > 正文

什么是浅拷贝和深拷贝

什么是浅拷贝和深拷贝

目录

一、概念

浅拷贝(Shallow Copy)

深拷贝(Deep Copy)

二、Java中使用工具来帮助进行对象的拷贝

⭐三、bean工具类

总结


一、概念

当涉及到对象复制时,浅拷贝和深拷贝是两个重要的概念。它们描述了在复制对象时如何处理对象内部的引用关系

浅拷贝(Shallow Copy

浅拷贝是指在复制对象时,只复制对象本身以及其内部的基本数据类型字段,而不复制对象内部的引用类型字段所引用的对象。换句话说,对于引用类型的字段,浅拷贝只复制了引用,而没有复制引用所指向的实际对象。

在这种拷贝中,只复制了原始对象的引用,而不是对象的实际内容。换句话说,原始对象和浅拷贝对象将共享相同的内存地址,因此对浅拷贝对象所做的任何更改也会反映在原始对象上。这通常适用于基本数据类型(如整数、浮点数等)以及引用类型(如字符串、数组、类对象等)。

举个例子,假设有一个包含引用类型字段的对象 A,进行浅拷贝后得到对象 B。如果修改对象 B 内部引用类型字段所指向的对象,这个修改也会影响到对象 A 内部相应的字段所引用的对象,因为它们实际上引用的是同一个对象。

深拷贝(Deep Copy)

深拷贝是指在复制对象时,不仅复制对象本身和其内部的基本数据类型字段,还递归地复制对象内部所有引用类型字段所引用的对象,从而创建全新的对象结构。深拷贝可以保证复制后的对象与原对象完全独立,对复制对象的修改不会影响原对象,反之亦然。这意味着原始对象和深拷贝对象将拥有独立的内存地址,对深拷贝对象所做的更改不会影响原始对象。深拷贝通常适用于包含复杂内部结构的对象(如包含嵌套对象的类)。

使用上面的例子,如果对对象 B 进行深拷贝,修改对象 B 内部引用类型字段所指向的对象不会影响对象 A 内部相应的字段所引用的对象,因为它们引用的是两个不同的对象。

以下是一个简单的示例代码来演示浅拷贝和深拷贝的区别:

  1. import java.util.ArrayList;
  2. import java.util.List;
  3. public class DeepCopyExample {
  4. public static void main(String[] args) {
  5. // 创建一个原始的 List 对象,包含三个字符串元素
  6. List<String> originalList = new ArrayList<>();
  7. originalList.add("元素 1");
  8. originalList.add("元素 2");
  9. originalList.add("元素 3");
  10. // 执行浅拷贝
  11. List<String> shallowCopy = shallowCopy(originalList);
  12. // 执行深拷贝
  13. List<String> deepCopy = deepCopy(originalList);
  14. // 更改原始列表中的元素
  15. originalList.set(0, "新元素 1");
  16. // 打印原始列表、浅拷贝列表和深拷贝列表的内容
  17. System.out.println("原始列表:" + originalList);
  18. System.out.println("浅拷贝列表:" + shallowCopy);
  19. System.out.println("深拷贝列表:" + deepCopy);
  20. }
  21. // 执行浅拷贝的方法
  22. public static <T> List<T> shallowCopy(List<T> listToCopy) {
  23. List<T> newList = new ArrayList<>();
  24. for (T item : listToCopy) {
  25. newList.add(item);
  26. }
  27. return newList;
  28. }
  29. // 执行深拷贝的方法
  30. public static <T> List<T> deepCopy(List<T> listToCopy) {
  31. List<T> newList = new ArrayList<>();
  32. for (T item : listToCopy) {
  33. // 如果元素是一个复杂对象,需要递归执行深拷贝
  34. if (item instanceof Cloneable) {
  35. try {
  36. T clonedItem = (T) item.getClass().getMethod("clone").invoke(item);
  37. newList.add(clonedItem);
  38. } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
  39. e.printStackTrace();
  40. }
  41. } else {
  42. // 如果元素是一个基本类型或不可拷贝的对象,直接添加到新列表中
  43. newList.add(item);
  44. }
  45. }
  46. return newList;
  47. }
  48. }

在这个示例中,我们创建了一个包含三个字符串元素的List对象originalList。然后,我们执行了浅拷贝和深拷贝操作,分别将originalList复制到shallowCopydeepCopy变量中。接着,我们更改了原始列表中的第一个元素,并打印出原始列表、浅拷贝列表和深拷贝列表的内容。

可以看到,浅拷贝列表和原始列表共享相同的内存地址,因此对原始列表的更改也会反映在浅拷贝列表中。而深拷贝列表则拥有独立的内存地址,对原始列表的更改不会影响深拷贝列表。

请注意,深拷贝对于包含复杂内部结构的对象(如包含嵌套对象的类)可能会比较复杂,因为需要递归地执行拷贝操作。此外,如果对象的类没有实现Cloneable接口,或者其clone()方法被覆盖并且没有正确实现拷贝逻辑,那么深拷贝可能无法正常工作。在这种情况下,你可能需要手动实现拷贝逻辑,或者使用其他方法来复制对象。

二、Java中使用工具来帮助进行对象的拷贝

以下是一些常见的工具和方法:

1. clone()方法:Cloneable接口中的clone()方法可以用来拷贝对象。如果一个类实现了Cloneable接口并正确重写了clone()方法,那么可以使用clone()方法来创建对象的副本。示例如下:

  1. public class MyClass implements Cloneable {
  2. // 定义字段
  3. private int field;
  4. // 构造函数
  5. public MyClass(int field) {
  6. this.field = field;
  7. }
  8. @Override
  9. public Object clone() {
  10. try {
  11. MyClass clonedObject = (MyClass) super.clone();
  12. return clonedObject;
  13. } catch (CloneNotSupportedException e) {
  14. e.printStackTrace();
  15. return null;
  16. }
  17. }
  18. public static void main(String[] args) {
  19. MyClass obj1 = new MyClass(10);
  20. MyClass obj2 = obj1.clone();
  21. System.out.println(obj1 == obj2); // false,输出为 false 表示 obj1 和 obj2 是不同的对象
  22. System.out.println(obj1.field == obj2.field); // true,输出为 true 表示 obj1 和 obj2 的 field 字段值相同
  23. }
  24. }

上述示例中,MyClass类实现了Cloneable接口,并正确重写了clone()方法。通过调用clone()方法,可以创建MyClass对象的副本。

2. ObjectInputStreamObjectOutputStream类:这些类位于java.io包中,可以用于将对象序列化到流中,然后再从流中反序列化回对象。示例如下:

  1. import java.io.*;
  2. public class DeepCopyExample {
  3. public static void main(String[] args) {
  4. // 创建一个原始的 MyClass 对象
  5. MyClass obj1 = new MyClass(10);
  6. // 将 obj1 对象序列化到流中
  7. try (ObjectOutputStream objOutputStream = new ObjectOutputStream(new FileOutputStream("object.ser"))) {
  8. objOutputStream.writeObject(obj1);
  9. } catch (IOException e) {
  10. e.printStackTrace();
  11. }
  12. // 从流中反序列化回对象
  13. try (ObjectInputStream objInputStream = new ObjectInputStream(new FileInputStream("object.ser"))) {
  14. MyClass obj2 = (MyClass) objInputStream.readObject();
  15. } catch (IOException | ClassNotFoundException e) {
  16. e.printStackTrace();
  17. }
  18. System.out.println(obj1 == obj2); // false,输出为 false 表示 obj1 和 obj2 是不同的对象
  19. System.out.println(obj1.field == obj2.field); // true,输出为 true 表示 obj1 和 obj2 的 field 字段值相同
  20. }
  21. }
  22. class MyClass implements Serializable {
  23. // 定义字段
  24. private int field;
  25. // 构造函数
  26. public MyClass(int field) {
  27. this.field = field;
  28. }
  29. }

上述示例中,MyClass类实现了Serializable接口。通过使用ObjectOutputStreamObjectInputStream,可以将MyClass对象序列化到文件中,然后再从文件中反序列化回对象。这也实现了对象的拷贝。

需要注意的是,对象的拷贝涉及到对象的状态和内容的复制。在进行拷贝时,需要确保拷贝的对象具有相同的状态和内容。以上介绍的方法适用于大部分情况下的对象拷贝,但在某些特殊情况下,可能需要根据具体需求进行更复杂的拷贝操作。

⭐三、bean工具类

在开发中可以自己封装一个通用的工具方法,用于将一个对象的属性复制到另一个对象中。以下参考b站up主三更草堂博客项目中封装的工具类,如下:

  1. public class BeanCopyUtils {
  2. private BeanCopyUtils() {
  3. }
  4. public static <V> V copyBean(Object source, Class<V> clazz) {
  5. //创建目标对象
  6. V result = null;
  7. try {
  8. result = clazz.newInstance();
  9. //实现属性copy
  10. copyProperties(source, result);
  11. } catch (Exception e) {
  12. e.printStackTrace();
  13. }
  14. //返回结果
  15. return result;
  16. }
  17. public static <O, V> List<V> copyBeanList(List<O> list, Class<V> clazz) {
  18. return list.stream()
  19. .map(o -> copyBean(o, clazz))
  20. .collect(Collectors.toList());
  21. }
  22. }

其中包含两个方法:

  1. copyBean 方法接收一个源对象和目标对象的类作为参数,通过反射创建目标对象,并将源对象的属性复制到目标对象中。
  2. copyBeanList 方法接收一个源对象列表和目标对象的类作为参数,使用 Java 8 的 Stream API 对列表中的每个对象调用 copyBean 方法,最终返回复制后的对象列表。

这种方式可以减少重复的属性复制代码,提高代码的复用性和可维护性。需要注意的是,该方法在处理属性复制时使用的是浅拷贝,如果需要深拷贝属性,可能需要额外的处理。

总结

  • 浅拷贝只复制对象本身和基本数据类型字段,引用类型字段仍然指向相同的对象。
  • 深拷贝除了复制对象本身和基本数据类型字段外,还递归复制所有引用类型字段所引用的对象,创建全新的对象结构。

浅拷贝只复制对象的引用,而深拷贝则递归复制了所有的值。选择使用哪种方式需要根据具体的需求,浅拷贝更高效但有引用相同的风险,深拷贝对于防止修改原对象很有用,但是相对来说更慢且更耗费内存。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/269106?site
推荐阅读
相关标签
  

闽ICP备14008679号