当前位置: 首页 > 科技观察

从原型模式到浅拷贝和深拷贝

时间:2023-03-16 10:27:08 科技观察

本文转载自微信公众号《JavaKeeper》,作者不造假。转载本文请联系JavaKeeper公众号。问题“如果你有一个对象并想完全复制它,你该怎么做?首先,你必须创建一个同类的新对象。然后,你必须迭代所有的成员变量原始对象,并将成员变量值复制到新对象for(inti=0;i<10;i++){Sheepsheep=newSheep("Shawn"+i+"number",2+i,"white");System.out.println(sheep.toString());}这个方法比较容易想到,但是有几个缺点,在创建新对象的时候,总是需要重新获取原对象的属性。如果创建的对象比较复杂,效率会降低非常低总是需要重新初始化对象,而不是动态获取对象的运行时状态,不够灵活另一方面,并??不是所有的对象都可以这样复制,因为有些对象可能有私有成员变量,这些成员变量在对象中Java中所有类的根类对象,是不可见的ooutside本身,提供了一个clone()方法,可以复制一个Java对象,但是需要实现clone()的类必须实现一个接口Cloneable,表示该类可以被复制,具有复制的能力。这导致原型模式。基本介绍原型模式(Prototypemode)是指:用一个原型实例指定要创建的对象的类型,通过复制这些原型来创建一个新的对象。原型模式是一种创造型设计模式,使您能够复制现有对象并且无需使代码依赖于它们所属的类。其工作原理是:通过向待创建对象传递一个原型对象,待创建对象通过要求原型对象复制自身来实现,即对象**.clone**()类图原型:Prototype接口将声明克隆方法。Java中的Prototype类需要满足以下两个条件才能实现Cloneable接口。Java语言中有一个Cloneable接口,它只有一个作用,就是在运行时通知虚拟机在实现这个接口的类上使用clone方法是安全的。在Java虚拟机中,只能复制实现该接口的类,否则会在运行时抛出CloneNotSupportedException。覆盖Object类中的克隆方法。在Java中,所有类的父类都是Object类,Object类中有一个clone方法,用于返回对象的一个??副本ConcretePrototype:具体原型(ConcretePrototype)类会实现clone方法.该方法除了将原始对象的数据复制到克隆中外,有时还需要处理克隆过程中的cornercase,例如克隆相关对象、整理递归依赖等。客户端:使用原型的客户端必须先获取原型instance对象,然后通过原型instance克隆自己来创建一个新的对象。Example》我们使用王二小的放羊例子来写这个例子1.原型类(Clonable的实现)@Setter@Getter@NoArgsConstructor@AllArgsConstructorclassSheepimplementsCloneable{privateStringname;privateIntegerage;privateStringcolor;@OverrideprotectedSheepclone(){Sheepsheep=null;try{sheep=(Sheep)super.clone();}catch(Exceptione){System.out.println(e.getMessage());}returnsheep;}}2、具体原型根据不同的业务实现不同的原型对象,假设主角是王二小,羊群里有一大群山羊和绵羊("LambsgoGrazing");}}3.ClientpublicclassClient{staticListsheepList=newArrayList<>();publicstaticvoidmain(String[]args){Goatgoat=newGoat();goat.setName("goat");goat.setAge(3);goat.setColor("gray");for(inti=0;i<5;i++){sheepList.add(goat.clone());}Lamblamb=newLamb();lamb.setName("羊肉");lamb.setAge(2);lamb.setColor("white");for(inti=0;i<5;i++){sheepList.add(lamb.clone());System.out.println(lamb.hashCode()+","+lamb.clone().hashCode());}for(Sheepsheep:sheepList){System.out.println(sheep.toString());}}原型模式将克隆委托给被克隆的实际对象的过程该模式为所有支持克隆的对象声明了一个公共接口,这允许您克隆对象而无需将您的代码耦合到对象所属的类。通常,这样的接口只包含一个克隆方法。所有类都非常相似地实现克隆方法。该方法会创建一个当前类的对象,然后将原对象的所有成员变量值复制到新创建的类中。您甚至可以复制私有成员变量,因为大多数编程语言都允许对象访问同类其他对象的私有成员变量。支持克隆的对象是原型。当你的对象有几十个成员变量和数百个类型时,克隆它甚至可以代替子类构造。优点使用原型模式创建对象比直接new一个对象在性能上要好很多,因为Object类的clone方法是本地方法,直接操作内存中的二进制流,尤其是复制大对象时,性能差异很明显。使用原型模式的另一个好处是简化了对象的创建,使得创建对象就像我们在编辑文档时复制粘贴一样简单。由于以上优点,当需要重复创建相似的对象时,可以考虑使用原型模式。例如,您需要在循环体中创建一个对象。如果对象创建过程复杂或者循环次数多,使用原型模式不仅可以简化创建过程,而且可以使系统的整体性能提高很多。适用场景《Head First 设计模式》对原型模式的描述如下:当创建给定类的实例的过程代价高昂或复杂时,使用原型模式。如果需要复制一些对象,但同时希望代码独立于这些对象所属的具体类,可以使用原型模式。如果子类仅在它们的对象初始化方式上有所不同,那么您可以使用此模式来减少子类的数量。别人创建这些子类的目的可能是为了创建特定类型的对象原型模式在Spring中的应用我们都知道Spring的bean默认是单例的,但是有些场景可能需要原型作用域,如下同样,王二小还有10只羊。有兴趣的还可以看看他们创建的对象是不是一样的{Objectbean=context.getBean("sheep");System.out.println(bean);}}}有兴趣的同学可以深入源码在具体实现中,在AbstractBeanFactory的doGetBean()方法中,注意事项原型模式使用原型模式复制对象,不会调用类的构造函数。因为对象的拷贝是通过调用Object类的clone方法完成的,它直接拷贝内存中的数据,所以没有调用类的构造函数。不仅构造函数中的代码不会被执行,连访问权限对于原型模式都是无效的。还记得单例模式吗?在单例模式中,只要将构造函数的访问权限设置为private,就可以实现单例。但是clone方法直接忽略了构造函数的权限,所以单例模式与原型模式有冲突,使用时要特别注意。深拷贝和浅拷贝。Object类的clone方法只会拷贝对象中的基本数据类型,不会拷贝数组、容器对象、引用对象等,属于浅拷贝。如果要实现深拷贝,就必须分别拷贝原型模式下的数组、容器对象、引用对象等。浅拷贝和深拷贝首先要明白,浅拷贝和深拷贝都是对一个已有对象的操作。在Java中,除了基本数据类型(元类型)之外,还有一种称为类实例对象的引用数据类型。通常,“=”号用于赋值操作。对于基本数据类型,实际上是复制了它的值,但是对于对象,实际上只是赋值了这个对象的引用。当原始对象的引用被传递时,它们实际上仍然指向同一个对象。浅拷贝和深拷贝的区别就是建立在这个基础上的。如果复制这个对象时,只复制基本数据类型,而引用数据类型只是引用传递,没有真正创建。新对象被认为是浅拷贝。反之,复制引用数据类型时,会创建一个新的对象,并复制其中的成员变量,这被认为是深复制。》所谓浅拷贝和深拷贝,只是在复制对象时,对类的实例对象的引用数据类型进行不同的操作。浅拷贝对于数据类型为基本数据类型的成员变量,浅拷贝会直接传递value,即将属性值复制到一个新的对象中,对于数据类型为引用数据类型的成员变量,比如成员变量是数组,某个类的对象等,那么浅层copy会按引用传递,即只是将成员变量的引用值(内存地址)复制到新的对象中。因为实际上两个对象的成员变量都指向同一个实例。在这种情况下,修改成员变量会影响另一个对象的成员变量的值,之前我们克隆羊是浅拷贝,如果我们给Sheep加上一个对象类型属性,publicSheepchild;可以看到s的friend和s1是一样的。Sheeps=newSheep();s.setName("sss");s.friend=newSheep();s.friend.setName("joyful");Sheeps1=s.clone();System.out.println(s==s1);System.out.println(s.hashCode()+"---"+s.clone().hashCode());System.out.println(s.friend==s1.friend);System.out.println(s.friend.hashCode()+"---"+s1.friend.hashCode());false621009875---1265094477true2125039532---2125039532深拷贝现在我们知道了clone()方法,只针对当前对象是浅拷贝,引用类型还是传引用。如何进行深拷贝?常见的深拷贝实现方式有两种:重写clone方法实现深拷贝和通过对象序列化实现深拷贝。浅拷贝和深拷贝只是相对的。如果一个对象内部只有基本数据类型,那么使用clone()方法获取这个对象的深拷贝,如果内部有引用数据类型,那么使用clone()方法就是浅拷贝操作。