• Java 拷贝


    Java 中的拷贝分为两种,浅拷贝和深拷贝,关于为什么要有两种拷贝方式而不是一种,就要涉及到 Java 的两种类型数据了。Java 的深浅拷贝都是针对于引用类型而言的,基本类型是没有深浅拷贝之分的,类似于 C++ 语言,浅拷贝中的引用有点像是 C++ 语言中指针。

    一、浅拷贝

    浅拷贝只会拷贝引用类型的引用,而并不会拷贝整个引用对象的全部内容。也就是说,拷贝过程中,只有引用对象在栈上的引用被复制了一份,其指向的内容并没有被复制,因此当新浅拷贝的对象内容被修改时,原来对象的内容也会被同时修改。

    Java 中的浅拷贝默认通过 Object 类的 clone 方法实现:

    1. import java.util.ArrayList;
    2. class ShallowCopyExample implements Cloneable { // 自定义类必须实现 Cloneable 接口
    3. public int num = 0; // 基本类型
    4. public String string = "shallow copy"; // 引用类型
    5. public ArrayList arrayList = new ArrayList<>(); // 引用类型
    6. @Override
    7. protected Object clone() throws CloneNotSupportedException {
    8. return super.clone(); // 直接用父类的,不做任何修改,为浅拷贝
    9. }
    10. }
    11. public class Test {
    12. public static void main(String[] args) throws CloneNotSupportedException {
    13. ShallowCopyExample shallowCopyExample = new ShallowCopyExample();
    14. ShallowCopyExample copiedObject = (ShallowCopyExample) shallowCopyExample.clone(); // 浅拷贝
    15. copiedObject.num = 1;
    16. copiedObject.string = "copied"; // String 不可修改,指向一个新的 String 对象
    17. copiedObject.arrayList.add(1); // 修改 arrayList 本身
    18. System.out.println(shallowCopyExample.num); // Output: 0(未修改)
    19. System.out.println(shallowCopyExample.string); // Output: shollow copy(未修改)
    20. System.out.println(shallowCopyExample.arrayList); // Output: [1](已修改)
    21. }
    22. }

    从上面的例子可以看出,浅拷贝会将对象的引用类型给同步修改,因为改的是引用对象的内容,并未改变引用本身。但上面的 String 也是引用对象,为何没有被更改呢?实际上面 copiedObject 的 String 类型字段是直接修改其引用,而非引用的内容,因为 String 类型不可更改,看似是引用对象的内容,实则为引用本身。

    总结一下:浅拷贝会将对象的全部内容都拷贝过来,但是对于其引用对象的内容,只会拷贝它们的引用,而不会拷贝它们的内容。

    这就相当于 C 语言的指针,复制了指针,但指针指向的那片内存空间并没有复制。

    以字段的拷贝为例,给个理解图就是:

    浅拷贝过程理解图

    Java 的浅拷贝实际上和 Python 的拷贝非常相似,Python 的内置类 list 便是如此,浅拷贝相当于一个新的引用,或者说别名。

    二、深拷贝

    浅拷贝是 Java 本身就有的,但有些时候我们不想要浅拷贝的这种效果,我们想要彻底完全的拷贝,也就是在拷贝引用对象的引用的同时,将其内容也给拷贝一份,以此来完全除去拷贝对象对原来对象的影响。我们可以用上面类似浅拷贝的理解图,以字段的拷贝为例来展现深拷贝:

    深拷贝过程理解图

     

    要达到这种效果,就必须实现深拷贝,也就是彻底的拷贝,但是很遗憾,Java 并没有像 Object 对象的 clone 方法那么方便的工具来实现深拷贝,想要实现深拷贝,可以采用的方法有:

    • 重写 clone 方法以实现深拷贝;
    • 采用序列化与反序列化的方式来达到类似深拷贝的效果;

    简单来说:深拷贝就是完全复制,无论是值类型还是引用类型。

    2.1 重写 clone 方法

    因为深拷贝必须使每一层的引用对象都被完全拷贝,因此拷贝过程中涉及到的每个引用类型都必须重写它们的 clone 方法(并非简单地继承父类 clone 方法)来实现深拷贝。下面是一个重写的案例:

    1. import java.util.ArrayList;
    2. class ShallowCopyExample implements Cloneable {
    3. public int num = 0; // 基本类型
    4. public String string = "shallow copy"; // 引用类型
    5. public ArrayList arrayList = new ArrayList<>(); // 引用类型
    6. @Override
    7. protected Object clone() throws CloneNotSupportedException {
    8. ShallowCopyExample shallowCopyExample = (ShallowCopyExample) super.clone();
    9. shallowCopyExample.arrayList = new ArrayList<>(arrayList); // 手动将 arrayList 复制一份
    10. return shallowCopyExample;
    11. }
    12. }
    13. public class Test {
    14. public static void main(String[] args) throws CloneNotSupportedException {
    15. ShallowCopyExample shallowCopyExample = new ShallowCopyExample();
    16. ShallowCopyExample copiedObject = (ShallowCopyExample) shallowCopyExample.clone();
    17. copiedObject.num = 1;
    18. copiedObject.string = "copied"; // String 不可修改,指向一个新的 String 对象
    19. copiedObject.arrayList.add(1); // 修改 arrayList 本身
    20. System.out.println(shallowCopyExample.num); // Output: 0(未修改)
    21. System.out.println(shallowCopyExample.string); // Output: shollow copy(未修改)
    22. System.out.println(shallowCopyExample.arrayList); // Output: [](未修改)
    23. }
    24. }

    从上面的代码中我们可以看出,深拷贝的实现并不是很难,但是当一个类中包含非常多不同类型的引用类型,每一个都要重写那将会是一件非常麻烦且繁琐的事情,这个时候,我们就要采取第二种方式了,也就是序列化。

    2.2 序列化与反序列化

    Java 序列化是一种将对象转换为字节流的过程,以便可以将对象保存到磁盘上,将其传输到网络上,或者将其存储在内存中,以后再进行反序列化,将字节流重新转换为对象。注意每个需要序列化的类都要实现 Serializable 接口。

    下面是一个序列化与反序列化的案例:

    1. import java.io.*;
    2. import java.util.ArrayList;
    3. class ShallowCopyExample implements Serializable { // 序列化对象必须实现 Serializable 接口
    4. public int num = 0; // 基本类型
    5. public String string = "deep copy"; // 引用类型
    6. public ArrayList arrayList = new ArrayList<>(); // 引用类型
    7. public ShallowCopyExample deepClone() {
    8. // 序列化
    9. try (
    10. FileOutputStream fileOutputStream = new FileOutputStream("object.ser"); // 对象文件为 object.ser
    11. ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
    12. ) {
    13. objectOutputStream.writeObject(this);
    14. } catch(IOException ioException) {
    15. ioException.printStackTrace();
    16. }
    17. // 反序列化
    18. try (
    19. FileInputStream fileInputStream = new FileInputStream("object.ser");
    20. ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
    21. ) {
    22. return (ShallowCopyExample) objectInputStream.readObject();
    23. } catch (IOException ioException) {
    24. ioException.printStackTrace();
    25. } catch (ClassNotFoundException e) {
    26. throw new RuntimeException(e);
    27. }
    28. // 失败返回 null
    29. return null;
    30. }
    31. }
    32. public class Test {
    33. public static void main(String[] args) throws CloneNotSupportedException {
    34. ShallowCopyExample shallowCopyExample = new ShallowCopyExample();
    35. ShallowCopyExample copiedObject = shallowCopyExample.deepClone(); // 改用自己定义的,用序列化实现的深拷贝方法
    36. copiedObject.num = 1;
    37. copiedObject.string = "copied"; // String 不可修改,指向一个新的 String 对象
    38. copiedObject.arrayList.add(1); // 修改 arrayList 本身
    39. System.out.println(shallowCopyExample.num); // Output: 0(未修改)
    40. System.out.println(shallowCopyExample.string); // Output: deep copy(未修改)
    41. System.out.println(shallowCopyExample.arrayList); // Output: [](未修改)
    42. }
    43. }

    因为序列化产生的是两个完全独立的对象,所有无论嵌套多少个引用类型,序列化都是能实现深拷贝的。用下面一张图可以很清晰地了解这一过程:

    序列化与反序列化

    序列化这一方法不仅仅在 Java 中有,在其他编程语言中同样拥有。实际上,序列化的主要作用并非深拷贝,而是用于将对象持久化,从而用于网络传输等。Python 中的内置模块 pickle 就是序列化相关的模块,然而 Python 中的深拷贝却无需使用 pickle 模块来实现,因为其内置模块 copy 中有一个 deepcopy 深拷贝函数,可以非常方便地得到大部分对象的深拷贝,希望有朝一日 Java 也能拥有如此方便的内置工具,而不再需要手动重写 clone 方法了。 

  • 相关阅读:
    请收藏:流固耦合经验总结(一)
    无语,程序在main方法执行和在junit单元测试结果居然不一致
    使用Lightrun对Java应用程序进行性能调整
    【刷题笔记9.25】LeetCode:合并二叉树
    Python Record
    Stm32_标准库_14_串口&蓝牙模块_解决手机与蓝牙模块数据传输的不完整性
    MTK6877/MT6877天玑900安卓5G核心板_安卓开发板主板定制开发
    【无标题】数字ic设计|ic芯片设计全流程
    Android Studio Gradle插件版本与Gradle 版本对应关系
    监听页面滚动位置定位底部按钮(包含页面初始化定位不对鼠标滑动生效的解决方案)
  • 原文地址:https://blog.csdn.net/weixin_62651706/article/details/133808076