实例分析java对象中浅克隆和深克隆

作者:laozhang 时间:2023-01-07 07:48:20 

引言:

在Object基类中,有一个方法叫clone,产生一个前期对象的克隆,克隆对象是原对象的拷贝,由于引用类型的存在,有深克隆和浅克隆之分,若克隆对象中存在引用类型的属性,深克隆会将此属性完全拷贝一份,而浅克隆仅仅是拷贝一份此属性的引用。首先看一下容易犯的几个小问题

clone方法是Object类的,并不是Cloneable接口的,Cloneable只是一个标记接口,标记接口是用用户标记实现该接口的类具有某种该接口标记的功能,常见的标记接口有三个:Serializable、Cloneable、RandomAccess,没有实现Cloneable接口,那么调用clone方法就会爆出CloneNotSupportedException异常。

Object类中的clone方法是protected修饰的,这就表明我们在子类中不重写此方法,就在子类外无法访问,因为这个protected权限是仅仅能在Object所在的包和子类能访问的,这也验证了子类重写父类方法权限修饰符可以变大但不能变小的说法。


protected native Object clone() throws CloneNotSupportedException;

重写clone方法,内部仅仅是调用了父类的clone方法,其实是为了扩大访问权限,当然你可以把protected改为public,以后再继承就不用重写了。当然只是浅克隆的clone函数,深克隆就需要修改了。


@Override  
protected Object clone() throws CloneNotSupportedException {    
return super.clone();
}

属性是String的情况,String也是一个类,那String引用类型吗?String的表现有的像基本类型,归根到底就是因为String不可改变,克隆之后俩个引用指向同一个String,但当修改其中的一个,改的不是String的值,却是新生成一个字符串,让被修改的引用指向新的字符串。外表看起来就像基本类型一样。

浅克隆:

浅克隆就是引用类型的属性无法完全复制,类User中包含成绩属性Mark,Mark是由Chinese和math等等组成的,浅克隆失败的例子


class Mark{
 private int chinese;
 private int math;
 public Mark(int chinese, int math) {
   this.chinese = chinese;
   this.math = math;
 }

public void setChinese(int chinese) {
   this.chinese = chinese;
 }

public void setMath(int math) {
   this.math = math;
 }

@Override
 public String toString() {
   return "Mark{" +
       "chinese=" + chinese +
       ", math=" + math +
       '}';
 }
}
public class User implements Cloneable{
 private String name;
 private int age;
 private Mark mark;

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

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

@Override
 protected Object clone() throws CloneNotSupportedException {
   return super.clone();
 }

public static void main(String[] args) throws CloneNotSupportedException {
   Mark mark = new Mark(100,99);
   User user = new User("user",22,mark);
   User userClone = (User) user.clone();
   System.out.println("原user:"+user);
   System.out.println("克隆的user:"+userClone);
   //修改引用类型的mark属性
   user.mark.setMath(60);
   System.out.println("修改后的原user:"+user);
   System.out.println("修改后的克隆user:"+userClone);
 }
}

输出结果为:

原user:User{name='user', age=22, mark=Mark{chinese=100, math=99}}
克隆的user:User{name='user', age=22, mark=Mark{chinese=100, math=99}}
修改后的原user:User{name='user', age=22, mark=Mark{chinese=100, math=60}}
修改后的克隆user:User{name='user', age=22, mark=Mark{chinese=100, math=60}}

很清楚的看到user的mark更改后,被克隆的user也修改了。而要想不被影响,就需要深克隆了。

深克隆:

方式一:clone函数的嵌套调用

既然引用类型无法被完全克隆,那将引用类型也实现Cloneable接口重写clone方法,在User类中的clone方法调用属性的克隆方法,也就是方法的嵌套调用


class Mark implements Cloneable{
 private int chinese;
 private int math;
 public Mark(int chinese, int math) {
   this.chinese = chinese;
   this.math = math;
 }
 public void setChinese(int chinese) {
   this.chinese = chinese;
 }
 public void setMath(int math) {
   this.math = math;
 }
 @Override
 protected Object clone() throws CloneNotSupportedException {
   return super.clone();
 }
 @Override
 public String toString() {
   return "Mark{" +
       "chinese=" + chinese +
       ", math=" + math +
       '}';
 }
}
public class User implements Cloneable{
 private String name;
 private int age;
 private Mark mark;

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

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

@Override
 protected Object clone() throws CloneNotSupportedException {
   User user = (User) super.clone();
   user.mark = (Mark) this.mark.clone();
   return user;
 }

public static void main(String[] args) throws CloneNotSupportedException {
   Mark mark = new Mark(100,99);
   User user = new User("user",22,mark);
   User userClone = (User) user.clone();
   System.out.println("原user:"+user);
   System.out.println("克隆的user:"+userClone);
   //修改引用类型的mark属性
   user.mark.setMath(60);
   System.out.println("修改后的原user:"+user);
   System.out.println("修改后的克隆user:"+userClone);
 }
}

输出结果为:

原user:User{name='user', age=22, mark=Mark{chinese=100, math=99}}
克隆的user:User{name='user', age=22, mark=Mark{chinese=100, math=99}}
修改后的原user:User{name='user', age=22, mark=Mark{chinese=100, math=60}}
修改后的克隆user:User{name='user', age=22, mark=Mark{chinese=100, math=99}}

方式二:序列化

上一种方法已经足够满足我们的需要,但是如果类之间的关系很多,或者是有的属性是数组呢,数组可无法实现Cloneable接口(我们可以在clone方法中手动复制数组),但是每次都得手写clone方法,很麻烦,而序列化方式只需要给每个类都实现一个Serializable接口,也是标记接口,最后同序列化和反序列化操作达到克隆的目的(包括数组的复制)。序列化和反序列化的知识请参照下一篇


import java.io.*;
class Mark implements Serializable {
 private int chinese;
 private int math;
 public Mark(int chinese, int math) {
   this.chinese = chinese;
   this.math = math;
}
 public void setChinese(int chinese) {
   this.chinese = chinese;
 }
 public void setMath(int math) {
   this.math = math;
 }
 @Override
 public String toString() {
   return "Mark{" +
       "chinese=" + chinese +
       ", math=" + math +
       '}';
 }
}
public class User implements Serializable{
 private String name;
 private int age;
 private Mark mark;

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

@Override
 public String toString() {
   return "User{" +
       "name='" + name + '\'' +
       ", age=" + age +
       ", mark=" + mark +
       '}';
 }
 public static void main(String[] args) throws IOException, ClassNotFoundException {
   Mark mark = new Mark(100,99);
   User user = new User("user",22,mark);

ByteArrayOutputStream bo = new ByteArrayOutputStream();
   ObjectOutputStream oo = new ObjectOutputStream(bo);
   oo.writeObject(user);//序列化
   ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
   ObjectInputStream oi = new ObjectInputStream(bi);
   User userClone = (User) oi.readObject();//反序列化

System.out.println("原user:"+user);
   System.out.println("克隆的user:"+userClone);
   user.mark.setMath(59);
   System.out.println("修改后的原user:"+user);
   System.out.println("修改后的克隆user:"+userClone);
 }
}

输出结果:

原user:User{name='user', age=22, mark=Mark{chinese=100, math=99}}
克隆的user:User{name='user', age=22, mark=Mark{chinese=100, math=99}}
修改后的原user:User{name='user', age=22, mark=Mark{chinese=100, math=60}}
修改后的克隆user:User{name='user', age=22, mark=Mark{chinese=100, math=99}}

带数组属性的克隆


import java.io.*;
import java.util.Arrays;

public class User implements Serializable{
 private String name;
 private int age;
 private int[] arr;

public User(String name, int age, int[] arr) {
   this.name = name;
   this.age = age;
   this.arr = arr;
 }
 @Override
 public String toString() {
   return "User{" +
       "name='" + name + '\'' +
       ", age=" + age +
       ", arr=" + Arrays.toString(arr) +
       '}';
 }
 public static void main(String[] args) throws IOException, ClassNotFoundException {
   int[] arr = {1,2,3,4,5,6};
   User user = new User("user",22,arr);

ByteArrayOutputStream bo = new ByteArrayOutputStream();
   ObjectOutputStream oo = new ObjectOutputStream(bo);
   oo.writeObject(user);//序列化
   ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
   ObjectInputStream oi = new ObjectInputStream(bi);
   User userClone = (User) oi.readObject();//反序列化

System.out.println("原user:"+user);
   System.out.println("克隆的user:"+userClone);
   user.arr[1] = 9;
   System.out.println("修改后的原user:"+user);
   System.out.println("修改后的克隆user:"+userClone);
 }
}
标签:java,浅克隆,深克隆
0
投稿

猜你喜欢

  • Java移动文件夹及其所有子文件与子文件夹

    2023-08-01 09:53:38
  • android选项卡TabHost功能用法详解

    2021-09-24 01:41:27
  • SpringBoot实战之高效使用枚举参数(原理篇)案例详解

    2022-02-10 23:54:23
  • Android实现密码明密文切换(小眼睛)

    2023-07-01 15:42:18
  • C# 泛型集合类List<T>使用总结

    2021-08-30 02:04:01
  • 深入解析C#设计模式编程中对建造者模式的运用

    2023-02-22 08:19:35
  • 讲解.NET环境下绘制模糊数学中隶属函数分布图第1/5页

    2022-10-04 10:21:51
  • Java实现根据模板自动生成新的PPT

    2022-10-22 18:31:31
  • C#使用iTextSharp操作PDF

    2023-07-29 02:03:11
  • Java 常见排序算法代码分享

    2023-09-30 08:23:09
  • C#使用Protocol Buffer(ProtoBuf)进行Unity中的Socket通信

    2021-10-21 09:08:21
  • Java中的3种输入方式实现解析

    2021-10-04 00:16:47
  • Android 异步获取网络图片并处理导致内存溢出问题解决方法

    2022-10-11 07:58:14
  • Android实现为Notification加上一个进度条的方法

    2022-12-14 00:39:01
  • SpringBoot中的五种对静态资源的映射规则的实现

    2023-06-21 08:31:47
  • C# 解决datagridview控件显示大量数据拖拉卡顿问题

    2022-03-21 12:52:24
  • 详解直接插入排序算法与相关的Java版代码实现

    2022-06-13 09:06:38
  • Java如何重写object类的equals方法详解

    2023-09-01 15:54:57
  • SpringBoot ResponseBody返回值处理的实现

    2023-06-29 23:18:43
  • Windows7下的Java运行环境搭建过程图解

    2022-03-14 19:40:59
  • asp之家 软件编程 m.aspxhome.com