Java 的拷贝(深拷贝、浅拷贝)

news/2024/11/27 9:25:58/

在对象的拷贝中,很多初学者可能搞不清到底是拷贝了引用还是拷贝了对象。在拷贝中这里就分为引用拷贝、浅拷贝、深拷贝进行讲述。

引用拷贝

引用拷贝会生成一个新的对象引用地址,但是两个最终指向依然是同一个对象。如何更好的理解引用拷贝呢?很简单,就拿我们人来说,通常有个姓名,但是不同场合、人物对我们的叫法可能不同,但我们很清楚哪些名称都是属于 "我" 的!

 通过一个代码示例让大家领略一下 (为了简便就不写 get、set 等方法):

class Son {String name;int age;public Son(String name, int age) {this.name = name;this.age = age;}
}public class test {public static void main(String[] args) {Son s1 = new Son("son1", 12);Son s2 = s1;s1.age = 22;System.out.println(s1);System.out.println(s2);System.out.println("s1的age:" + s1.age);System.out.println("s2的age:" + s2.age);System.out.println("s1==s2" + (s1 == s2));//相等}
}

测试结果:

Son@135fbaa4
Son@135fbaa4
s1的age:22
s2的age:22
true

浅拷贝

如何创建一个对象,将目标对象的内容复制过来而不是直接拷贝引用呢?

这里先讲一下浅拷贝,浅拷贝会创建一个新对象,新对象和原对象本身没有任何关系,新对象和原对象不等,但是新对象的属性和老对象相同。具体可以看如下区别:

  • 如果属性是基本类型 (int,double,long,boolean 等),拷贝的就是基本类型的值;

  • 如果属性是引用类型,拷贝的就是内存地址(即复制引用但不复制引用的对象) ,因此如果其中一个对象改变了这个地址,就会影响到另一个对象。

如果用一张图来描述一下浅拷贝,它应该是这样的:

如何实现浅拷贝呢?也很简单,就是在需要拷贝的类上实现 Cloneable 接口并重写其 clone () 方法

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

 在使用的时候直接调用类的 clone () 方法即可。具体案例如下:

class Father{String name;public Father(String name) {this.name=name;}@Overridepublic String toString() {return "Father{" +"name='" + name + '\'' +'}';}
}class Son implements Cloneable {int age;String name;Father father;public Son(String name,int age) {this.age=age;this.name = name;}public Son(String name,int age, Father father) {this.age=age;this.name = name;this.father = father;}@Overridepublic String toString() {return "Son{" +"age=" + age +", name='" + name + '\'' +", father=" + father +'}';}@Overrideprotected Son clone() throws CloneNotSupportedException {// 字段father指向同一个引用。return (Son) super.clone();}
}public class test {public static void main(String[] args) throws CloneNotSupportedException {Father f=new Father("bigFather");Son s1 = new Son("son1",13);s1.father=f;Son s2 = s1.clone();System.out.println(s1);System.out.println(s2);System.out.println("s1==s2:"+(s1 == s2));//不相等System.out.println("s1.name==s2.name:"+(s1.name == s2.name));//相等System.out.println();//但是他们的Father father 和String name的引用一样s1.age=12;s1.father.name="smallFather";//s1.father引用未变s1.name="son222";//类似 s1.name=new String("son222") 引用发生变化System.out.println("s1.Father==s2.Father:"+(s1.father == s2.father));//相等System.out.println("s1.name==s2.name:"+(s1.name == s2.name));//不相等System.out.println(s1);System.out.println(s2);}
}

运行结果:

Son{age=13, name='son1', father=Father{name='bigFather'}}
Son{age=13, name='son1', father=Father{name='bigFather'}}
s1==s2:false
s1.name==s2.name:true//此时相等s1.Father==s2.Father:true
s1.name==s2.name:false//修改引用后不等
Son{age=12, name='son222', father=Father{name='smallFather'}}
Son{age=13, name='son1', father=Father{name='smallFather'}}

不出意外,这种浅拷贝除了对象本身不同以外,各个零部件和关系和拷贝对象都是相同的,就好像双胞胎一样,是两个人,但是其开始的样貌、各种关系 (父母亲人) 都是相同的。需要注意的是其中 name 初始 == 是相等的,是因为初始浅拷贝它们指向一个相同的 String,而后 s1.name="son222" 则改变引用指向。

深拷贝

对于上述的问题虽然拷贝的两个对象不同,但其内部的一些引用还是相同的,怎么样绝对的拷贝这个对象,使这个对象完全独立于原对象呢?就使用我们的深拷贝了。深拷贝:在对引用数据类型进行拷贝的时候,创建了一个新的对象,并且复制其内的成员变量。

 

在具体实现深拷贝上,这里提供两个方式,重写 clone () 方法和序列法。

重写 clone () 方法

如果使用重写 clone () 方法实现深拷贝,那么要将类中所有自定义引用变量的类也去实现 Cloneable 接口实现 clone () 方法。对于字符类可以创建一个新的字符串实现拷贝。

对于上述代码,Father 类实现 Cloneable 接口并重写 clone () 方法。son 的 clone () 方法需要对各个引用都拷贝一遍

//Father clone()方法
@Override
protected Father clone() throws CloneNotSupportedException {return (Father) super.clone();
}//Son clone()方法
@Override
protected Son clone() throws CloneNotSupportedException {Son son= (Son) super.clone();//待返回克隆的对象son.name=new String(name);son.father=father.clone();return son;
}

 其他代码不变,执行结果如下:

Son{age=13, name='son1', father=Father{name='bigFather'}}
Son{age=13, name='son1', father=Father{name='bigFather'}}
s1==s2:false
s1.name==s2.name:falses1.Father==s2.Father:false
s1.name==s2.name:false
Son{age=12, name='son222', father=Father{name='smallFather'}}
Son{age=13, name='son1', father=Father{name='bigFather'}}

序列化

可以发现这种方式实现了深拷贝。但是这种情况有个问题,如果引用数量或者层数太多了怎么办呢?

不可能去每个对象挨个写 clone () 吧?那怎么办呢?借助序列化啊。

因为序列化后:将二进制字节流内容写到一个媒介(文本或字节数组),然后是从这个媒介读取数据,原对象写入这个媒介后拷贝给 clone 对象,原对象的修改不会影响 clone 对象,因为 clone 对象是从这个媒介读取。

熟悉对象缓存的知道我们经常将 Java 对象缓存到 Redis 中,然后还可能从 Redis 中读取生成 Java 对象,这就用到序列化和反序列化。一般可以将 Java 对象存储为字节流或者 json 串然后反序列化成 Java 对象。因为序列化会储存对象的属性但是不会也无法存储对象在内存中地址相关信息。所以在反序列化成 Java 对象时候会重新创建所有的引用对象。

在具体实现上,自定义的类需要实现 Serializable 接口。在需要深拷贝的类 (Son) 中定义一个函数返回该类对象:

 

protected Son deepClone() throws IOException, ClassNotFoundException {Son son=null;//在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组中//默认创建一个大小为32的缓冲区ByteArrayOutputStream byOut=new ByteArrayOutputStream();//对象的序列化输出ObjectOutputStream outputStream=new ObjectOutputStream(byOut);//通过字节数组的方式进行传输outputStream.writeObject(this);  //将当前student对象写入字节数组中//在内存中创建一个字节数组缓冲区,从输入流读取的数据保存在该字节数组缓冲区ByteArrayInputStream byIn=new ByteArrayInputStream(byOut.toByteArray()); //接收字节数组作为参数进行创建ObjectInputStream inputStream=new ObjectInputStream(byIn);son=(Son) inputStream.readObject(); //从字节数组中读取return  son;
}

使用时候调用我们写的方法即可,其他不变,实现的效果为:

Son{age=13, name='son1', father=Father{name='bigFather'}}
Son{age=13, name='son1', father=Father{name='bigFather'}}
s1==s2:false
s1.name==s2.name:falses1.Father==s2.Father:false
s1.name==s2.name:false
Son{age=12, name='son222', father=Father{name='smallFather'}}
Son{age=13, name='son1', father=Father{name='bigFather'}}

注:2023/07/01 对重写clone方法示例。

package learnjavafx8;import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;/*** @copyright 2023-2022* @package   learnjavafx8* @file      Person.java* @date      2023-07-01 14:34* @author    qiao wei* @version   1.0* @brief     * @history*/
public class Person implements Cloneable {public static void main(String[] args) {Person p01 = new Person(10, 20, true);System.out.println(p01);Person p02 = (Person) p01.clone();p02.firstIntegerProperty.set(-233);p02.secondIntegerProperty.set(666);p02.flag = false;System.out.println(p01);System.out.println(p02);}public Person() {firstIntegerProperty = new SimpleIntegerProperty();secondIntegerProperty = new SimpleIntegerProperty();flag = false;}public Person(int first, int second, boolean flag) {firstIntegerProperty = new SimpleIntegerProperty(first);secondIntegerProperty = new SimpleIntegerProperty(second);this.flag = flag;}@Overrideprotected Object clone() {Person person = null;try {person = (Person) super.clone();person.firstIntegerProperty =new SimpleIntegerProperty(this.firstIntegerProperty.get());person.secondIntegerProperty =new SimpleIntegerProperty(this.secondIntegerProperty.get());person.flag = this.flag;} catch (CloneNotSupportedException exception) {exception.printStackTrace();} finally {return person;}}@Overridepublic String toString() {return "Person{" +"firstIntegerProperty=" + firstIntegerProperty +", secondIntegerProperty=" + secondIntegerProperty +", flag=" + flag +'}';}public IntegerProperty firstIntegerProperty = null;public IntegerProperty secondIntegerProperty = null;public boolean flag = false;
}

测试结果,对p02引用字段的修改并没有影响到p01引用字段。进行了深拷贝。

Person{firstIntegerProperty=IntegerProperty [value: 10], secondIntegerProperty=IntegerProperty [value: 20], flag=true}
Person{firstIntegerProperty=IntegerProperty [value: 10], secondIntegerProperty=IntegerProperty [value: 20], flag=true}
Person{firstIntegerProperty=IntegerProperty [value: -233], secondIntegerProperty=IntegerProperty [value: 666], flag=false}Process finished with exit code 0

修改Person类中clone方法,将深拷贝改为浅拷贝(删除引用类型数据copy内容),其余代码不变:

	@Overrideprotected Object clone() {Person person = null;try {person = (Person) super.clone();//			person.firstIntegerProperty =
//				new SimpleIntegerProperty(this.firstIntegerProperty.get());
//			person.secondIntegerProperty =
//				new SimpleIntegerProperty(this.secondIntegerProperty.get());
//			person.flag = this.flag;} catch (CloneNotSupportedException exception) {exception.printStackTrace();} finally {return person;}}

运行结果:

Person{firstIntegerProperty=IntegerProperty [value: 10], secondIntegerProperty=IntegerProperty [value: 20], flag=true}
Person{firstIntegerProperty=IntegerProperty [value: -233], secondIntegerProperty=IntegerProperty [value: 666], flag=true}
Person{firstIntegerProperty=IntegerProperty [value: -233], secondIntegerProperty=IntegerProperty [value: 666], flag=false}Process finished with exit code 0

p02引用字段的修改影响到p01的引用字段内容。


http://www.ppmy.cn/news/671287.html

相关文章

php获取判断图片大小,php获取图片详细信息(宽 高 格式 大小)

function getImageInfo($img) //$img为图像文件绝对路径 { $img_info getimagesize($img); switch ($img_info[2]) { case 1: $imgtype "GIF"; break; case 2: $imgtype "JPG"; break; case 3: $imgtype "PNG"; break; } $img_type $imgtyp…

npy文件的读出与修改

npy 文件用于存储重建ndarray 所需的数据、图形、dtype 和其他信息。 常用的IO 函数有: load() 和save() 函数是读写文件数组数据的两个主要函数,默认情况下,数组是以未压缩的原始二进制格式保存在扩展名为. npy 的文件中。 1.首先自己写一个.npy文件,使用np.save(‘test.…

华为鸿蒙与海尔,国产家电巨头力挺华为,强强联合,为鸿蒙系统开疆拓土

鸿蒙依托美的电器,直接进入国际市场 在众多意想不到的收获当中,最让华为期许便是国际市场。国内市场有人支持鸿蒙,但国外市场被苹果和安卓占有,没有一系列卓越的品牌去刺激,是很难找到自己立足的市场。 美的国际形象良…

海外净利润低?海尔智家H股上市有望看齐国内!

文/易牟 来源/螳螂财经(ID:TanglangFin) 12月20日,海尔智家发布了一则新公告,关于对海尔电器私有化议案将于12月21日(百慕大时间)生效。海尔智家也预期于12月23日上午9点在香港联交所挂牌上市&#xff0c…

私有化预期终于落地,海尔智家将迎来更多可能!

作者/易牟 来源/螳螂财经(ID:TanglangFin) 12月9日,海尔电器发布公告称,特别股东大会以99.99%的赞成票顺利通过私有化议案。而在此前9月1日,海尔智家股东大会就已经以99%的高票通过议案。 这里有一个细节——两大上…

海尔智家股市被看好,增长逻辑令人深思

21世纪以来,中国家电行业沧海桑田。“做大”是第1个十年的主旋律,国内及国外企业的竞争与合作,推动我国家电工业总产值从2000亿增长到近万亿;第2个十年的关键词是“普及”,在家电下乡政策助力下,我国家庭白…

疫情影响海外净利润?海尔智家的回答出人意料

文/螳螂财经(ID:TanglangFin) 作者/易牟 整合上市一个措施完成后,海尔智家市值净增2000亿。不过,对长期投资者来说,这只是市值大涨的第一个措施,海外市场的盈利能力也是其中一个措施。 海外疫情蔓延&…

海尔微型计算机云悦mini2a,全能小轻新 云悦mini2A升级大赏

沈佳宜变身雷母版小龙女,一口气毁掉那些年追过的小清新,狂遭网友吐槽。小主机云悦mini升级全能小轻新,京东首发大赏300元,吸引剁手党纷纷预约。12月3日-12月9日,新品云悦mini 2A/云悦mini 2S京东999元开约,…