对象克隆学习

假如说你想复制一个简单变量。很简单:

int apples = 5;  
int pears = apples;  

不仅仅是int类型,其它七种原始数据类型(boolean,char,byte,short,float,double.long)同样适用于该类情况。

但是如果你复制的是一个对象,情况就有些复杂了。

假设说我是一个beginner,我会这样写:

复制代码

class Student {  private int number;  public int getNumber() {  return number;  }  public void setNumber(int number) {  this.number = number;  }  }  
public class Test {  public static void main(String args[]) {  Student stu1 = new Student();  stu1.setNumber(12345);  Student stu2 = stu1;  System.out.println("学生1:" + stu1.getNumber());  System.out.println("学生2:" + stu2.getNumber());  }  
}  

复制代码

结果:

学生1:12345  

学生2:12345  

这里我们自定义了一个学生类,该类只有一个number字段。

我们新建了一个学生实例,然后将该值赋值给stu2实例。(Student stu2 = stu1;)

再看看打印结果,作为一个新手,拍了拍胸腹,对象复制不过如此,

难道真的是这样吗?

我们试着改变stu2实例的number字段,再打印结果看看:

stu2.setNumber(54321);  System.out.println("学生1:" + stu1.getNumber());  
System.out.println("学生2:" + stu2.getNumber());  

结果:

学生1:54321  

学生2:54321  

这就怪了,为什么改变学生2的学号,学生1的学号也发生了变化呢?

原因出在(stu2 = stu1) 这一句。该语句的作用是将stu1的引用赋值给stu2,

这样,stu1和stu2指向内存堆中同一个对象。如图:

那么,怎样才能达到复制一个对象呢?

是否记得万类之王Object。它有11个方法,有两个protected的方法,其中一个为clone方法。

在Java中所有的类都是缺省的继承自Java语言包中的Object类的,查看它的源码,你可以把你的JDK目录下的src.zip复制到其他地方然后解压,里面就是所有的源码。发现里面有一个访问限定符为protected的方法clone():

复制代码

/*
Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object.
The general intent is that, for any object x, the expression:
1) x.clone() != x will be true
2) x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements.
3) x.clone().equals(x) will be true, this is not an absolute requirement.
*/
protected native Object clone() throws CloneNotSupportedException;

复制代码

仔细一看,它还是一个native方法,大家都知道native方法是非Java语言实现的代码,供Java程序调用的,因为Java程序是运行在JVM虚拟机上面的,要想访问到比较底层的与操作系统相关的就没办法了,只能由靠近操作系统的语言来实现。

  1. 第一次声明保证克隆对象将有单独的内存地址分配。
  2. 第二次声明表明,原始和克隆的对象应该具有相同的类类型,但它不是强制性的。
  3. 第三声明表明,原始和克隆的对象应该是平等的equals()方法使用,但它不是强制性的。

因为每个类直接或间接的父类都是Object,因此它们都含有clone()方法,但是因为该方法是protected,所以都不能在类外进行访问。

要想对一个对象进行复制,就需要对clone方法覆盖。

回到顶部

为什么要克隆?

  大家先思考一个问题,为什么需要克隆对象?直接new一个对象不行吗?

  答案是:克隆的对象可能包含一些已经修改过的属性,而new出来的对象的属性都还是初始化时候的值,所以当需要一个新的对象来保存当前对象的“状态”就靠clone方法了。那么我把这个对象的临时属性一个一个的赋值给我新new的对象不也行嘛?可以是可以,但是一来麻烦不说,二来,大家通过上面的源码都发现了clone是一个native方法,就是快啊,在底层实现的。

  提个醒,我们常见的Object a=new Object();Object b;b=a;这种形式的代码复制的是引用,即对象在内存中的地址,a和b对象仍然指向了同一个对象。

  而通过clone方法赋值的对象跟原来的对象时同时独立存在的。

回到顶部

如何实现克隆

先介绍一下两种不同的克隆方法,浅克隆(ShallowClone)深克隆(DeepClone)

在Java语言中,数据类型分为值类型(基本数据类型)和引用类型,值类型包括int、double、byte、boolean、char等简单数据类型,引用类型包括类、接口、数组等复杂类型。浅克隆和深克隆的主要区别在于是否支持引用类型的成员变量的复制,下面将对两者进行详细介绍。

一般步骤是(浅克隆):

1. 被复制的类需要实现Clonenable接口(不实现的话在调用clone方法会抛出CloneNotSupportedException异常), 该接口为标记接口(不含任何方法)

2. 覆盖clone()方法,访问修饰符设为public方法中调用super.clone()方法得到需要的复制对象。(native为本地方法)

下面对上面那个方法进行改造:

复制代码

class Student implements Cloneable{  private int number;  public int getNumber() {  return number;  }  public void setNumber(int number) {  this.number = number;  }  @Override  public Object clone() {  Student stu = null;  try{  stu = (Student)super.clone();  }catch(CloneNotSupportedException e) {  e.printStackTrace();  }  return stu;  }  
}  
public class Test {  public static void main(String args[]) {  Student stu1 = new Student();  stu1.setNumber(12345);  Student stu2 = (Student)stu1.clone();  System.out.println("学生1:" + stu1.getNumber());  System.out.println("学生2:" + stu2.getNumber());  stu2.setNumber(54321);  System.out.println("学生1:" + stu1.getNumber());  System.out.println("学生2:" + stu2.getNumber());  }  
}  

复制代码

结果:

学生1:12345  

学生2:12345  

学生1:12345  

学生2:54321

如果你还不相信这两个对象不是同一个对象,那么你可以看看这一句:

System.out.println(stu1 == stu2); // false  

上面的复制被称为浅克隆。

还有一种稍微复杂的深度复制:

我们在学生类里再加一个Address类。

复制代码

 1 class Address  {  2     private String add;  3   4     public String getAdd() {  5         return add;  6     }  7   8     public void setAdd(String add) {  9         this.add = add;  
10     }  
11       
12 }  
13   
14 class Student implements Cloneable{  
15     private int number;  
16   
17     private Address addr;  
18       
19     public Address getAddr() {  
20         return addr;  
21     }  
22   
23     public void setAddr(Address addr) {  
24         this.addr = addr;  
25     }  
26   
27     public int getNumber() {  
28         return number;  
29     }  
30   
31     public void setNumber(int number) {  
32         this.number = number;  
33     }  
34       
35     @Override  
36     public Object clone() {  
37         Student stu = null;  
38         try{  
39             stu = (Student)super.clone();  
40         }catch(CloneNotSupportedException e) {  
41             e.printStackTrace();  
42         }  
43         return stu;  
44     }  
45 }  
46 public class Test {  
47       
48     public static void main(String args[]) {  
49           
50         Address addr = new Address();  
51         addr.setAdd("杭州市");  
52         Student stu1 = new Student();  
53         stu1.setNumber(123);  
54         stu1.setAddr(addr);  
55           
56         Student stu2 = (Student)stu1.clone();  
57           
58         System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());  
59         System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());  
60     }  
61 }  

复制代码

结果:

学生1:123,地址:杭州市  

学生2:123,地址:杭州市  

乍一看没什么问题,真的是这样吗?

我们在main方法中试着改变addr实例的地址。

addr.setAdd("西湖区");  System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());  
System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());  

结果:

学生1:123,地址:杭州市  
学生2:123,地址:杭州市  
学生1:123,地址:西湖区  
学生2:123,地址:西湖区  

这就奇怪了,怎么两个学生的地址都改变了?

原因是浅复制只是复制了addr变量的引用,并没有真正的开辟另一块空间,将值复制后再将引用返回给新对象。

所以,为了达到真正的复制对象,而不是纯粹引用复制。我们需要将Address类可复制化,并且修改clone方法,完整代码如下:

复制代码

 1 package abc;  2   3 class Address implements Cloneable {  4     private String add;  5   6     public String getAdd() {  7         return add;  8     }  9   
10     public void setAdd(String add) {  
11         this.add = add;  
12     }  
13       
14     @Override  
15     public Object clone() {  
16         Address addr = null;  
17         try{  
18             addr = (Address)super.clone();  
19         }catch(CloneNotSupportedException e) {  
20             e.printStackTrace();  
21         }  
22         return addr;  
23     }  
24 }  
25   
26 class Student implements Cloneable{  
27     private int number;  
28   
29     private Address addr;  
30       
31     public Address getAddr() {  
32         return addr;  
33     }  
34   
35     public void setAddr(Address addr) {  
36         this.addr = addr;  
37     }  
38   
39     public int getNumber() {  
40         return number;  
41     }  
42   
43     public void setNumber(int number) {  
44         this.number = number;  
45     }  
46       
47     @Override  
48     public Object clone() {  
49         Student stu = null;  
50         try{  
51             stu = (Student)super.clone();   //浅复制  
52         }catch(CloneNotSupportedException e) {  
53             e.printStackTrace();  
54         }  
55         stu.addr = (Address)addr.clone();   //深度复制  
56         return stu;  
57     }  
58 }  
59 public class Test {  
60       
61     public static void main(String args[]) {  
62           
63         Address addr = new Address();  
64         addr.setAdd("杭州市");  
65         Student stu1 = new Student();  
66         stu1.setNumber(123);  
67         stu1.setAddr(addr);  
68           
69         Student stu2 = (Student)stu1.clone();  
70           
71         System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());  
72         System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());  
73           
74         addr.setAdd("西湖区");  
75           
76         System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());  
77         System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());  
78     }  
79 }  

复制代码

结果:

学生1:123,地址:杭州市  
学生2:123,地址:杭州市  
学生1:123,地址:西湖区  
学生2:123,地址:杭州市  

这样结果就符合我们的想法了。

最后我们可以看看API里其中一个实现了clone方法的类:

java.util.Date:

复制代码

/** * Return a copy of this object. */  
public Object clone() {  Date d = null;  try {  d = (Date)super.clone();  if (cdate != null) {  d.cdate = (BaseCalendar.Date) cdate.clone();  }  } catch (CloneNotSupportedException e) {} // Won't happen  return d;  
}  

复制代码

该类其实也属于深度复制。

参考文档:Java如何复制对象

回到顶部

浅克隆和深克隆

1、浅克隆

在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象;如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。

简单来说,在浅克隆中,当对象被复制时只复制它本身和其中包含的值类型的成员变量,而引用类型的成员对象并没有复制。

在Java语言中,通过覆盖Object类的clone()方法可以实现浅克隆

2、深克隆

在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。

简单来说,在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。

在Java语言中,如果需要实现深克隆,可以通过覆盖Object类的clone()方法实现,也可以通过序列化(Serialization)等方式来实现。

如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。

序列化就是将对象写到流的过程,写到流中的对象是原有对象的一个拷贝,而原对象仍然存在于内存中。通过序列化实现的拷贝不仅可以复制对象本身,而且可以复制其引用的成员对象,因此通过序列化将对象写到一个流中,再从流里将其读出来,可以实现深克隆。需要注意的是能够实现序列化的对象其类必须实现Serializable接口,否则无法实现序列化操作。

扩展
Java语言提供的Cloneable接口和Serializable接口的代码非常简单,它们都是空接口,这种空接口也称为标识接口,标识接口中没有任何方法的定义,其作用是告诉JRE这些接口的实现类是否具有某个功能,如是否支持克隆、是否支持序列化等。

回到顶部

解决多层克隆问题

如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。

复制代码

 1 public class Outer implements Serializable{2   private static final long serialVersionUID = 369285298572941L;  //最好是显式声明ID3   public Inner inner;4  //Discription:[深度复制方法,需要对象及对象所有的对象属性都实现序列化] 5   public Outer myclone() {6       Outer outer = null;7       try { // 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝8           ByteArrayOutputStream baos = new ByteArrayOutputStream();9           ObjectOutputStream oos = new ObjectOutputStream(baos);
10           oos.writeObject(this);
11       // 将流序列化成对象
12           ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
13           ObjectInputStream ois = new ObjectInputStream(bais);
14           outer = (Outer) ois.readObject();
15       } catch (IOException e) {
16           e.printStackTrace();
17       } catch (ClassNotFoundException e) {
18           e.printStackTrace();
19       }
20       return outer;
21   }
22 }

复制代码

Inner也必须实现Serializable,否则无法序列化:

复制代码

 1 public class Inner implements Serializable{2   private static final long serialVersionUID = 872390113109L; //最好是显式声明ID3   public String name = "";4 5   public Inner(String name) {6       this.name = name;7   }8 9   @Override
10   public String toString() {
11       return "Inner的name值为:" + name;
12   }
13 }

复制代码

这样也能使两个对象在内存空间内完全独立存在,互不影响对方的值。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/599518.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

vue插件--xterm封装

安装 npm install xterm xterm-addon-fit -D两种模式 log:日志输出shell:终端命令 <template><div :id"id" class"xterm"></div> </template> <script> import { defineComponent, onMounted, onBeforeUnmount, watch, n…

Centos7.9和Debian12部署Minio详细流程

一、安装minio Centos wget https://dl.min.io/server/minio/release/linux-amd64/archive/minio-20230227181045.0.0.x86_64.rpm -O minio.rpm sudo dnf install minio.rpmDebian wget https://dl.min.io/server/minio/release/linux-amd64/archive/minio_20230227181045.0…

axios的七大特性

1、在浏览器中发送 XMLHttpRequests 请求&#xff1b; 2、在 node.js 中发送 http请求&#xff1b; 3、基于 promise 的 HTTP 库&#xff0c;支持promise所有的API 4、拦截请求和响应&#xff1b;&#xff08;修改请求数据&#xff0c;只能用在PUT,POST和PATCH这几个请求方法&…

代理(Proxy)模式

代理&#xff08;Proxy&#xff09;模式介绍 作用&#xff1a;通过代理可以控制访问某个对象的方法&#xff0c;在调用这个方法前做前置处理&#xff0c;调用这个方法后做后置处理。&#xff08;即&#xff1a; AOP的微观实现&#xff01;&#xff09; 核心角色 抽象角色(接口…

C++初级(三)

我们这里还是在C基础上进行对C基础语法的补充学习&#xff01; 一.C数组 C数组大体和C相同&#xff0c;但是C数组还是有一定的区别的。 我们先看看C和C数组共同的易错点知识&#xff1a; 1.数组只能一次初始化。 2.如果对数组只进行一部分初始化&#xff0c;那么其他未初始…

PHP写一个函数能够遍历一个文件夹下的所有文件和子文件夹

社区版本可以用 function myscandir($dir) { $files array(); if ( $handle opendir($dir) ) { while ( ($file readdir($handle)) ! false ) { if ( $file ! ".." && $file ! "." ) { if ( isdir($dir . "/" . $file) ) { $…

[ABC334E] Christmas Color Grid 1

洛谷题目链接 Atcoder题目链接 分析 发现将每个红色连通块涂成绿色连通块后&#xff0c;绿色连通块个数会加一&#xff0c;但是如果这个连通块之前已经跟绿色连通块相邻&#xff0c;则连通块数量减一。 代码 #include <bits/stdc.h> #define int long longusing nam…

11. 线程

11. 线程 1. 线程概述1.1 线程概念1.1.1 什么是线程1.1.2 线程是如何创建起来的1.1.3 线程的特点1.1.4 线程与进程 1.2 并发和并行 2. 线程 ID3. 创建线程4. 终止线程5. 回收线程6. 取消线程6.1 取消一个线程6.2 取消状态以及类型6.3 取消点6.4 线程可取消性的检测 7. 分离线程…

【React系列】Hook(一)基本使用

本文来自#React系列教程&#xff1a;https://mp.weixin.qq.com/mp/appmsgalbum?__bizMzg5MDAzNzkwNA&actiongetalbum&album_id1566025152667107329) 一. 认识hook 1.1. 为什么需要hook Hook 是 React 16.8 的新增特性&#xff0c;它可以让我们在不编写class的情况下…

OpenFeign相关问题及答案(2024)

1、什么是OpenFeign&#xff0c;它如何简化远程服务调用&#xff1f; OpenFeign是一个声明式的Web服务客户端&#xff0c;它使得编写HTTP客户端变得更加容易。它属于Spring Cloud Netflix项目的一部分&#xff0c;可以与Spring Boot应用轻松集成。通过使用OpenFeign&#xff0…

群晖Docker部署HomeAssistant容器结合内网穿透远程控制家中智能设备

目录 一、下载HomeAssistant镜像 二、内网穿透HomeAssistant&#xff0c;实现异地控制智能家居 三、使用固定域名访问HomeAssistant 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。 点击跳转到网站 Ho…

Guava Cache 异步刷新技巧,你值得拥有!

以下文章来源于勇哥Java实战 &#xff0c;作者勇哥 Guava Cache是一款非常优秀的本地缓存框架。 这篇文章&#xff0c;我们聊聊如何使用 Guava Cache 异步刷新技巧带飞系统性能 。 1 经典配置 Guava Cache 的数据结构跟 JDK1.7 的 ConcurrentHashMap 类似&#xff0c;提供了基…

citeSpace保姆级安装使用教程

citeSpace保姆级安装使用教程 文章目录 citeSpace保姆级安装使用教程CiteSpace功能与参数区安装使用知网数据导出citespace数据导入结果 设置操作隐藏节点 CiteSpace功能与参数区 安装 citeSpace安装教程 citespace下载 网址&#xff1a;https://citespace.podia.com/ 安装之…

大A又跌了

才开盘几天&#xff0c;又开始下跌了。生活更加苦难。期待高深算法。

18、Kubernetes核心技术 - InitContainer(初始化容器)

目录 一、概述 二、使用InitContainer 一、概述 InitContainer即初始化容器&#xff0c;是 K8S官方为我们提供的一个可以用来判断环境是否已经满足运行 Pod 应用前所需要的条件。 比如我们有一个应用&#xff0c;需要部署到Tomcat环境&#xff0c;那么在部署这个应用Pod之前…

STM32使用中断方式进行USART数据收发以及printf函数的重写

时间记录&#xff1a;2024/1/5 一、USART/UART介绍 协议介绍 &#xff08;1&#xff09;起始位&#xff0c;一位逻辑电平0表示 &#xff08;2&#xff09;数据位&#xff0c;8-9位&#xff0c;逻辑高低电平&#xff0c;一般使用8位 &#xff08;3&#xff09;校验位&#xff…

DevOps(5)

目录 21.如何在Linux下访问分区&#xff1f; 22.什么是硬链接&#xff1f; 23.Linux下文件名的最大长度是多少&#xff1f; 24.什么是以点开头的文件名&#xff1f; 25.解释虚拟桌面&#xff1f; 21.如何在Linux下访问分区&#xff1f; Linux在驱动器标识符的末尾分配数字…

如何获取时间戳?

获取现在的时间0时0秒 一、JavasCRIPT时间转时间戳 JavaScript获得时间戳的方法有五种&#xff0c;后四种都是通过实例化时间对象new Date() 来进一步获取当前的时间戳&#xff0c;JavaScript处理时间主要使用时间对象Date Date.now()可以获得当前的时间戳&#xff1a; con…

2-sql注入之sqli-labs靶场搭建

文章目录 SQL注入之sqli-labs靶场搭建1、Sqli-labs环境安装需要安装以下环境工具下载链接&#xff1a; 2、phpstudy连接mysql总是启动了又停止第一种情况可能是端口占用问题第二种情况就是曾经在电脑上安装过mysql SQL注入之sqli-labs靶场搭建 Sqli-labs是一个印度程序员写的&…

vmware中ubuntu虚拟机不能够用共享文件夹

有时候发现装好虚拟机后&#xff0c;然后 虚拟机-设置-选项-共享文件夹 然后使用快捷键ctrlaltt 打开命令行&#xff0c;cd /mnt下没有看到hgfs文件夹 解决办法是安装vmware tools工具 此时想通过点击 虚拟机-安装vmwaretools工具 按钮 居然发现该按钮是灰色的&#xff0…