经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » Java » 查看文章
浅拷贝、深拷贝与序列化【初级Java必需理解的概念】
来源:cnblogs  作者:救苦救难韩天尊  时间:2024/6/17 15:10:33  对本文有异议

浅拷贝

首先创建两个类,方便理解浅拷贝

  1. @Data
  2. class Student implements Cloneable{
  3. //年龄和名字是基本属性
  4. private int age;
  5. private String name;
  6. //书包是引用属性
  7. private Bag bag;
  8. public Student(int age, String name, Bag bag) {
  9. this.age = age;
  10. this.name = name;
  11. this.bag = bag;
  12. }
  13. @Override
  14. public String toString() {
  15. return "age=" + age + ", name='" + name + ", bag=" + bag;
  16. }
  17. @Override
  18. protected Object clone() throws CloneNotSupportedException {
  19. return super.clone();
  20. }
  21. }
  1. @Data
  2. class Bag {
  3. private String color;
  4. private int price;
  5. public Bag(String color, int price) {
  6. this.color = color;
  7. this.price = price;
  8. }
  9. @Override
  10. public String toString() {
  11. return "color='" + color + ", price=" + price;
  12. }
  13. }

Cloneable 接口只是一个标记接口(没属性和方法):

  1. public interface Cloneable {
  2. }

标记接口的作用其实很简单,用来表示某个功能在执行的时候是合法的。

如果不实现Cloneable接口直接重写并调用clone()方法,会抛出 CloneNotSupportedException 异常。

测试类

  1. class TestClone {
  2. public static void main(String[] args) throws CloneNotSupportedException {
  3. Student student1 = new Student(18, "张三", new Bag("红",100));
  4. Student student2 = (Student) student1.clone();
  5. System.out.println("浅拷贝后:");
  6. System.out.println("student1:" + student1);
  7. System.out.println("student2:" + student2);
  8. //修改非引用类型属性name
  9. student2.setName("李四");
  10. //修改引用类型属性bag
  11. Bag bag = student2.getBag();
  12. bag.setColor("蓝");
  13. bag.setPrice(200);
  14. System.out.println("修改了 student2 的 name 和 bag 后:");
  15. System.out.println("student1:" + student1);
  16. System.out.println("student2:" + student2);
  17. }
  18. }
  19. //打印结果
  20. 浅拷贝后:
  21. student1age=18, name='张三, bag=color='红, price=100
  22. student2age=18, name='张三, bag=color='红, price=100
  23. 修改了 student2 name bag 后:
  24. student1age=18, name='张三, bag=color='蓝, price=200
  25. student2age=18, name='李四, bag=color='蓝, price=200

可以看得出,浅拷贝后:

修改了student2的非引用类型属性name,student1的name并不会跟着改变

但修改了student2的引用类型属性bag,student1的bag跟着改变了

说明浅拷贝克隆的对象中,引用类型的字段指向的是同一个,当改变任何一个对象,另外一个对象也会随之改变。

深拷贝

深拷贝和浅拷贝不同的,深拷贝中的引用类型字段也会克隆一份,当改变任何一个对象,另外一个对象不会随之改变。

例子

  1. @Data
  2. class Bag implements Cloneable {
  3. private String color;
  4. private int price;
  5. public Bag(String color, int price) {
  6. this.color = color;
  7. this.price = price;
  8. }
  9. @Override
  10. public String toString() {
  11. return "color='" + color + ", price=" + price;
  12. }
  13. @Override
  14. protected Object clone() throws CloneNotSupportedException {
  15. return super.clone();
  16. }
  17. }

注意,此时的 Bag 类和浅拷贝时不同,重写了 clone() 方法,并实现了 Cloneable 接口。为的就是深拷贝的时候也能够克隆该字段。

  1. @Data
  2. class Student implements Cloneable{
  3. //年龄和名字是基本属性
  4. private int age;
  5. private String name;
  6. //书包是引用属性
  7. private Bag bag;
  8. public Student(int age, String name, Bag bag) {
  9. this.age = age;
  10. this.name = name;
  11. this.bag = bag;
  12. }
  13. @Override
  14. public String toString() {
  15. return "age=" + age + ", name='" + name + ", bag=" + bag;
  16. }
  17. @Override
  18. protected Object clone() throws CloneNotSupportedException {
  19. Student s = (Student) super.clone();
  20. s.setBag((Bag) s.getBag().clone());
  21. return s;
  22. }
  23. }

注意,此时 Student 类也与之前的不同,clone() 方法当中,不再只调用 Object 的 clone() 方法对 Student 进行克隆了,还对 Bag 也进行了克隆。

来看测试类

  1. class TestClone {
  2. public static void main(String[] args) throws CloneNotSupportedException {
  3. Student student1 = new Student(18, "张三", new Bag("红",100));
  4. Student student2 = (Student) student1.clone();
  5. System.out.println("深拷贝后:");
  6. System.out.println("student1:" + student1);
  7. System.out.println("student2:" + student2);
  8. //修改非引用类型属性name
  9. student2.setName("李四");
  10. //修改引用类型属性bag
  11. Bag bag = student2.getBag();
  12. bag.setColor("蓝");
  13. bag.setPrice(200);
  14. System.out.println("修改了 student2 的 name 和 bag 后:");
  15. System.out.println("student1:" + student1);
  16. System.out.println("student2:" + student2);
  17. }
  18. }
  19. //这个测试类和之前的浅拷贝的测试类一样,但运行结果是不同的。
  20. 深拷贝后:
  21. student1age=18, name='张三, bag=color='红, price=100
  22. student2age=18, name='张三, bag=color='红, price=100
  23. 修改了 student2 name bag 后:
  24. student1age=18, name='张三, bag=color='红, price=100
  25. student2age=18, name='李四, bag=color='蓝, price=200

不只是 student1 和 student2 是不同的对象,它们中的 bag 也是不同的对象。所以,改变了 student2 中的 bag 并不会影响到 student1。

不过,通过 clone() 方法实现的深拷贝比较笨重,因为要将所有的引用类型都重写 clone() 方法。

更好的方法是利用序列化

序列化

序列化是将对象写入流中,而反序列化是将对象从流中读取出来。写入流中的对象就是对原始对象的拷贝。需要注意的是,每个要序列化的类都要实现 Serializable 接口,该接口和 Cloneable 接口类似,都是标记型接口。

来看例子

  1. @Data
  2. class Bag implements Serializable {
  3. private String color;
  4. private int price;
  5. public Bag(String color, int price) {
  6. this.color = color;
  7. this.price = price;
  8. }
  9. @Override
  10. public String toString() {
  11. return "color='" + color + ", price=" + price;
  12. }
  13. }

Bag 需要实现 Serializable 接口

  1. @Data
  2. class Student implements Serializable {
  3. //年龄和名字是基本属性
  4. private int age;
  5. private String name;
  6. //书包是引用属性
  7. private Bag bag;
  8. public Student(int age, String name, Bag bag) {
  9. this.age = age;
  10. this.name = name;
  11. this.bag = bag;
  12. }
  13. @Override
  14. public String toString() {
  15. return "age=" + age + ", name='" + name + ", bag=" + bag;
  16. }
  17. //使用序列化拷贝
  18. public Object serializeClone() throws IOException, ClassNotFoundException {
  19. // 序列化
  20. ByteArrayOutputStream bos = new ByteArrayOutputStream();
  21. ObjectOutputStream oos = new ObjectOutputStream(bos);
  22. oos.writeObject(this);
  23. // 反序列化
  24. ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
  25. ObjectInputStream ois = new ObjectInputStream(bis);
  26. return ois.readObject();
  27. }
  28. }

Student 类也需要实现 Serializable 接口,并且在该类中,增加了一个 serializeClone() 的方法,利用 OutputStream 进行序列化,InputStream 进行反序列化,这样就实现了深拷贝。

来看示例

  1. class TestClone {
  2. public static void main(String[] args) throws CloneNotSupportedException, IOException, ClassNotFoundException {
  3. Student student1 = new Student(18, "张三", new Bag("红",100));
  4. Student student2 = (Student) student1.serializeClone();
  5. System.out.println("浅拷贝后:");
  6. System.out.println("student1:" + student1);
  7. System.out.println("student2:" + student2);
  8. //修改非引用类型属性name
  9. student2.setName("李四");
  10. //修改引用类型属性bag
  11. Bag bag = student2.getBag();
  12. bag.setColor("蓝");
  13. bag.setPrice(200);
  14. System.out.println("修改了 student2 的 name 和 bag 后:");
  15. System.out.println("student1:" + student1);
  16. System.out.println("student2:" + student2);
  17. }
  18. }
  19. //与之前测试类不同的是,调用了 serializeClone() 方法。
  20. 浅拷贝后:
  21. student1age=18, name='张三, bag=color='红, price=100
  22. student2age=18, name='张三, bag=color='红, price=100
  23. 修改了 student2 name bag 后:
  24. student1age=18, name='张三, bag=color='红, price=100
  25. student2age=18, name='李四, bag=color='蓝, price=200

测试结果和之前用 clone() 方法实现的深拷贝一样。

clone() 方法同时是一个本地(native)方法,它的具体实现会交给 HotSpot 虚拟机,那就意味着虚拟机在运行该方法的时候,会将其替换为更高效的 C/C++ 代码,进而调用操作系统去完成对象的克隆工作。

需要注意,由于是序列化涉及到输入流和输出流的读写,在性能上要比 HotSpot 虚拟机实现的 clone() 方法差很多。

原文链接:https://www.cnblogs.com/GilbertDu/p/18245960

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号