【面试篇】 Java对象拷贝(对象克隆 对象复制)
发布日期:2021-06-29 15:34:32 浏览次数:2 分类:技术文章

本文共 7812 字,大约阅读时间需要 26 分钟。

Java对象拷贝(对象克隆 对象复制)

1.前言

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

int apples = 5;  int pears = apples;

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

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

但假如是个新手,可能会这样写。

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

但我们试着改变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指向内存堆中同一个对象。如图:

2.为什么要克隆

为什么需要克隆对象呢?直接new一个对象可以吗?

克隆的对象可能包含一些已经修改过的属性,而new出来的对象的属性都还是初始化时候的值,所以当需要一个新的对象来保存当前的“状态”就靠克隆方法。

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

3.如何实现对象克隆

介绍两种不同的克隆方法:浅克隆和深克隆。

(1)浅克隆

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

  • 覆盖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

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

(2)深克隆

还在学生类里再加一个Address类。

class Address  {
private String add; public String getAdd() {
return add; } public void setAdd(String add) {
this.add = add; }}class Student implements Cloneable{
private int number; private Address addr; public Address getAddr() {
return addr; } public void setAddr(Address addr) {
this.addr = addr; } 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[]) {
Address addr = new Address(); addr.setAdd("杭州市"); Student stu1 = new Student(); stu1.setNumber(123); stu1.setAddr(addr); Student stu2 = (Student)stu1.clone(); System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd()); }}

结果:

学生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方法,完整代码如下:

package abc;class Address implements Cloneable {
private String add; public String getAdd() {
return add; } public void setAdd(String add) {
this.add = add; } @Override public Object clone() {
Address addr = null; try{
addr = (Address)super.clone(); }catch(CloneNotSupportedException e) {
e.printStackTrace(); } return addr; }}class Student implements Cloneable{
private int number; private Address addr; public Address getAddr() {
return addr; } public void setAddr(Address addr) {
this.addr = addr; } 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(); } stu.addr = (Address)addr.clone(); //深度复制 return stu; }}public class Test {
public static void main(String args[]) {
Address addr = new Address(); addr.setAdd("杭州市"); Student stu1 = new Student(); stu1.setNumber(123); stu1.setAddr(addr); Student stu2 = (Student)stu1.clone(); System.out.println("学生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd()); System.out.println("学生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd()); 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,地址:杭州市

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

(3)浅克隆和深克隆

浅克隆

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ARj2RIbL-1607171705687)(D:\software\typora\workplace\imgs_clone\2.png)]

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

深克隆

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

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zPH7Xndw-1607171705688)(D:\software\typora\workplace\imgs_clone\3.png)]

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

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

(4)解决多层克隆问题

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

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

public class Inner implements Serializable{
private static final long serialVersionUID = 872390113109L; //最好是显式声明ID public String name = ""; public Inner(String name) {
this.name = name; } @Override public String toString() {
return "Inner的name值为:" + name; }}
public class Outer implements Serializable{
private static final long serialVersionUID = 369285298572941L; //最好是显式声明ID public Inner inner; //Discription:[深度复制方法,需要对象及对象所有的对象属性都实现序列化] public Outer myclone() {
Outer outer = null; try {
// 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝 ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(this);      // 将流序列化成对象 ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); outer = (Outer) ois.readObject(); } catch (IOException e) {
e.printStackTrace(); } catch (ClassNotFoundException e) {
e.printStackTrace(); } return outer; }}

转载地址:https://codingchaozhang.blog.csdn.net/article/details/110723883 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:【Leetcode刷题篇】leetcode64 最小路径和
下一篇:【面试篇】Java的代理模式-静态代理和动态代理详解

发表评论

最新留言

感谢大佬
[***.8.128.20]2024年04月20日 07时42分28秒