一文看懂深克隆和浅克隆的关系抽象原型接口,自定义类型只需要实现这个接口,重写Object.clone()方法即可完成对这个类的复制。通过查看JDK的源码可以发现,Cloneable其实是一个空接口。Java之所以提供Cloneable接口,只是为了在运行时通知Java虚拟机,可以安全地对该类使用clone()方法。而如果类没有实现Cloneable接口,调用clone()方法会抛出CloneNotSupportedException异常。一般来说,如果使用clone()方法,必须满足以下条件。(1)对于任何对象o,都有o.clone()!=o。换句话说,克隆对象与原型对象不是同一个对象。(2)对于任意对象o,都有o.clone().getClass()==o.getClass()。换句话说,复制的对象与原始对象属于同一类型。(3)如果正确定义了对象o的equals()方法,那么应该建立o.clone().equals(o)。我们在设计自定义类的clone()方法时,应该遵守这三个条件。一般来说,这3个条件中的前2个是必需的,第3个是可选的。下面使用Java提供的API应用实现原型模式,代码如下。classClient{publicstaticvoidmain(String[]args){//创建原型对象ConcretePrototypetype=newConcretePrototype("original");System.out.println(类型);//复制原型对象ConcretePrototypecloneType=type.clone();cloneType.desc="克隆";System.out.println(克隆类型);}staticclassConcretePrototypeimplementsCloneable{privateStringdesc;publicConcretePrototype(Stringdesc){this.desc=desc;}@OverrideprotectedConcretePrototypeclone(){ConcretePrototypecloneType=null;尝试{cloneType=(ConcretePrototype)super.clone();}catch(CloneNotSupportedExceptione){e.printStackTrace();}返回克隆类型;}@OverridepublicStringtoString(){return"ConcretePrototype{"+"desc='"+desc+'\''+'}';}}}super.clone()方法直接以二进制流的形式从堆内存中复制,重新分配一个内存块,所以效率非常高。由于super.clone()方法是基于内存复制的,所以不会调用对象的构造函数,即不需要经过初始化过程。在日常开发中,使用super.clone()方法并不能满足所有需求。如果类中有引用对象属性,原型对象和克隆对象的属性会指向同一个对象的引用。@DatapublicclassConcretePrototypeimplementsCloneable{privateintage;私有字符串名称;私人列表爱好;@OverridepublicConcretePrototypeclone(){try{return(ConcretePrototype)super.clone();.printStackTrace();返回空值;}}@OverridepublicStringtoString(){return"ConcretePrototype{"+"age="+age+",name='"+name+'\''+",hobbies="+hobbies+'}';}}修改客户端测试代码。publicstaticvoidmain(String[]args){//创建原型对象ConcretePrototypeprototype=newConcretePrototype();原型.setAge(18);prototype.setName("汤姆");Listhobbies=newArrayList();hobbies.add("书法");hobbies.add("艺术");prototype.setHobbies(爱好);System.out.println(原型);//复制原型对象ConcretePrototypecloneType=prototype.clone();cloneType.getHobbies().add("技术控");System.out.println("原型对象:"+prototype);System.out.println("克隆对象:"+cloneType);我们给copy对象添加了一个属性hobbies(爱好)后,发现原型对象也发生了变化,这显然不符合预期。因为我们希望复制的对象应该是原型对象中的两个独立的对象,没有任何联系。从测试结果来看,应该是hobbies共享一个内存地址,也就是说复制的不是值,而是引用的地址。这样,如果我们修改任何对象中的属性值,protoType和cloneType的hobbies值都会改变。这就是我们常说的浅克隆。只完整复制值类型数据,不分配引用对象。也就是说,所有的引用对象仍然指向原来的对象,这显然不是我们想要的结果。那么如何解决这个问题呢?Java自带的clone()方法进行浅克隆。而如果我们要进行深度克隆,我们可以直接在super.clone()之后手动再分配一块内存给复制对象的相关属性,但是如果原型对象维护了很多引用属性,手动分配就会很麻烦。因此,在Java中,如果要完成原型对象的深度克隆,通常会使用Serializable方法。2使用序列化实现深度克隆在上一节的基础上继续改造,增加了一个deepClone()方法。/***由汤姆创建。*/@DatapublicclassConcretePrototypeimplementsCloneable,Serializable{privateintage;私有字符串名称;私人列表爱好;@OverridepublicConcretePrototypeclone(){try{return(ConcretePrototype)super.clone();}catch(CloneNotSupportedExceptione){e.printStackTrace();返回空值;}}publicConcretePrototypedeepClone(){try{ByteArrayOutputStreambos=newByteArrayOutputStream();ObjectOutputStreamoos=newObjectOutputStream(bos);oos.writeObject(这个);ByteArrayInputStreambis=newByteArrayInputStream(bos.toByteArray());ObjectInputStreamois=newObjectInputStream(bis);返回(具体原型)ois.readObject();}catch(Exceptione){e.printStackTrace();返回空值;}}@Override公共StringtoString(){return"ConcretePrototype{"+"age="+age+",name='"+name+'\''+",hobbies="+hobbies+'}';}}客户端调用代码如下publicstaticvoidmain(String[]args){//创建原型对象ConcretePrototypeprototype=newConcretePrototype();原型.setAge(18);prototype.setName("汤姆");Listhobbies=newArrayList();hobbies.add("书法");hobbies.add("艺术");prototype.setHobbies(爱好);//复制原型对象ConcretePrototypecloneType=prototype.deepCloneHobbies();cloneType.getHobbies().add("技术控");System.out.println("原型对象:"+prototype);System.out.println("克隆对象:"+cloneType);System.out.println(prototype==cloneType);System.out.println("原型对象爱好:"+prototype.getHobbies());System.out.println("克隆对象兴趣爱好:"+cloneType.getHobbies());System.out.println(prototype.getHobbies()==cloneType.getHobbies());}运行程序,得到如下图所示的结果,与预期结果一致。从运行结果来看,我们确实完成了深度克隆。关注微信公众号『汤姆炸弹架构』回复“设计模式”获取完整源码。【推荐】汤姆炸弹架构:30个设计模式实战案例(附源码),挑战60W年薪不是梦技术在于分享,我分享我的快乐!如果本文对您有帮助,请关注并点赞;有什么建议也可以留言或私信。您的支持是我坚持创作的动力。关注微信公众号『汤姆炸弹建筑』,获取更多技术干货!