是时候看看Java的深拷贝和浅拷贝了。转载本文请联系bigsai公众号。在开发、测验、面试中,我们可能会遇到将一个对象的属性赋值给另一个对象的情况。这种情况称为复制。复制与Java的内存结构密切相关。了解Java的深浅拷贝很有必要!在对象的复制中,很多初学者可能分不清是复制引用还是复制对象。在拷贝中,分为参考拷贝、浅拷贝、深拷贝。ReferencecopyReferencecopy会生成一个新的对象引用地址,但是两个final指向的还是同一个对象。如何更好地理解参考文案?这很简单。以我们为例,我们通常都有一个名字,但不同的场合和人可能对我们的称呼不同,但我们知道哪些名字属于“我”!当然,通过一个代码示例让大家尝尝(为了简单起见,get,set等方法就不写了):classSon{Stringname;intage;publicSon(Stringname,intage){this.name=name;this.age=age;}}publicclasstest{publicstaticvoidmain(String[]args){Sons1=newSon("son1",12);Sons2=s1;s1.age=22;System.out.println(s1);System.out.println(s2);System.out.println("s1的年龄:"+s1.age);System.out.println("s2的年龄:"+s2.age);System.out.println("s1==s2"+(s1==s2));//等于}}输出结果为:age:22trueofSon@135fbaa4Son@135fbaa4s1age:22trueofs2如何创建对象并复制目标对象的内容而不是直接复制引用??先说浅拷贝。浅拷贝会创建一个新对象。新对象与原始对象本身无关。新对象不等于原对象,但新对象的属性与旧对象相同。具体可以看出以下区别:如果属性是基本类型(int、double、long、boolean等),则复制基本类型的值;如果属性是引用类型,则复制内存地址(即复制引用但不复制引用对象),因此如果其中一个对象更改了这个地址,则会影响另一个对象。如果用一张图来描述浅拷贝,应该是这样的:浅拷贝是如何实现的?也很简单,就是在需要复制的类上实现Cloneable接口,重写它的clone()方法。@OverrideprotectedObjectclone()throwsCloneNotSupportedException{returnsuper.clone();}使用时直接调用该类的clone()方法。具体实例如下:classFather{Stringname;publicFather(Stringname){this.name=name;}@OverridepublicStringtoString(){return"Father{"+"name='"+name+'\''+'}';}}classSonimplementsCloneable{intage;Stringname;Fatherfather;publicSon(Stringname,intage){this.age=age;this.name=name;}publicSon(Stringname,intage,Fatherfather){this.age=age;this.name=name;this.father=father;}@OverridepublicStringtoString(){return"Son{"+"age="+age+",name='"+name+'\''+",father="+father+'}';}@OverrideprotectedSonclone()throwsCloneNotSupportedException{return(Son)super.clone();}}publicclasstest{publicstaticvoidmain(String[]args)throwsCloneNotSupportedException{Fatherf=newFather("bigFather");Sons1=newSon("son1",13);s1.father=f;Sons2=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();//但是他们的Fatherfather和Stringname的引用是一样的s1.age=12;s1.father.name="smallFather";//s1.father引用并没有改变s1.name="son222";//类似s1.name=newString("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:falses1.name==s2.name:true//此时等于s1.Father==s2.Father:trues1.name==s2.name:false//修改引用后不等于Son{age=12,name='son222',father=Father{name='smallFather'}}Son{age=13,name='son1',father=Father{name='smallFather'}}不出所料,这种浅拷贝不仅和对象本身不同,而且部分和关系都和被拷贝的对象一样,只是liketwins,两个人,但是他们的初始长相和各种关系(父母亲戚)是一样的,需要注意的是初始name==是相等的,因为初始浅拷贝指向同一个String,然后s1.name="son222"更改参考点。深拷贝针对上面的问题,虽然两次拷贝的对象不同,但是一些内部引用还是一样的。如何制作这个对象的绝对副本,使这个对象完全独立于原对象?只需使用我们的深拷贝。深拷贝:当拷贝一个引用数据类型时,会创建一个新的对象并拷贝它的成员变量。在深拷贝的具体实现中,这里有两个方法,重写了clone()方法和sequence方法。重写clone()方法如果使用重写clone()方法实现深拷贝,那么类中所有使用自定义引用变量的类也必须实现Cloneable接口来实现clone()方法。对于字符类,可以创建一个新字符串用于复制。对于上面的代码,Father类实现了Cloneable接口,重写了clone()方法。儿子的clone()方法需要复制每个引用。//Fatherclone()方法@OverrideprotectedFatherclone()throwsCloneNotSupportedException{return(Father)super.clone();}//Sonclone()方法@OverrideprotectedSonclone()throwsCloneNotSupportedException{Sonson=(Son)super.clone();//待返回复制的对象son.name=newString(name);son.father=father.clone();returnson;}其他代码不变,执行结果如下:Son{age=13,name='son1',father=Father{name='bigFather'}}Son{age=13,name='son1',father=Father{name='bigFather'}}s1==s2:falses1.name==s2.name:falses1.Father==s2.Father:falses1.name==s2.name:falseSon{age=12,name='son222',father=Father{name='smallFather'}}Son{age=13,name='son1',father=Father{name='bigFather'}}序列化可以发现这个方法实现了深拷贝。但是在这种情况下有一个问题。如果引用或图层太多怎么办?不可能为每个对象一个一个地写clone()?那我们该怎么办呢?使用序列化。因为序列化后:将二进制字节流内容写入一个介质(文本或字节数组),然后从这个介质中读取数据,将原始对象写入到这个介质中,然后复制到克隆对象中,对原始对象的修改不会影响克隆对象,因为克隆对象是从该介质中读取的。熟悉对象缓存的都知道,我们经常将Java对象缓存在Redis中,然后从Redis中读取并生成Java对象,其中使用了序列化和反序列化。通常,Java对象可以存储为字节流或json字符串,然后反序列化为Java对象。因为序列化会存储对象的属性,但不会也不能存储对象在内存中的地址信息。所以当反序列化为Java对象时,所有的引用对象都会被重新创建。具体实现上,自定义类需要实现Serializable接口。在类(Son)中定义一个需要深拷贝返回该类对象的函数:protectedSondeepClone()throwsIOException,ClassNotFoundException{Sonson=null;//在内存中创建一个字节数组缓冲区,保存所有发送到的数据outputstreamInthisbytearray//默认创建一个大小为32的bufferByteArrayOutputStreambyOut=newByteArrayOutputStream();//对象的序列化输出this);//将当前学生对象写入字节数组//在内存中创建字节数组缓冲区,将从输入流读取的数据保存到字节数组缓冲区中ByteArrayInputStreambyIn=newByteArrayInputStream(byOut.toByteArray());//接收一个字节数组作为参数创建ObjectInputStreaminputStream=newObjectInputStream(byIn);son=(Son)inputStream.readObject();//从字节数组中读取returnson;}调用我们写的时候使用方法可以be用过,其他不变。实现的效果是:Son{age=13,name='son1',father=Father{name='bigFather'}}Son{age=13,name='son1',father=Father{name='bigFather'}}s1==s2:falses1.name==s2.name:falses1.Father==s2.Father:falses1.name==s2.name:falseSon{age=12,name='son222',father=Father{name='smallFather'}}Son{age=13,name='son1',father=Father{name='bigFather'}}原文链接:https://mp.weixin.qq.com/s/kmD6FKJ5fwLUr64DonZf-A
