For investors
股价:
5.36 美元 %For investors
股价:
5.36 美元 %认真做教育 专心促就业
前言
国际惯例,本文仍然是在学习设计模式的路上所写,希望对同样在学习设计模式的童靴有点作用,大牛误入的话还请给点宝贵意见,感激不尽。
对象引用复制
假设现在有一个学生类如下所示:
publicclassStudentimplementsCloneable{privateString ame;privateString sex;privateintage;publicStudent(String name,String sex,intage){this.name = name;this.sex = sex;this.age = age; } }
我们可能经常看到这样的代码:
Student stu =newStudent("zhangsan","nan",10); Student stu1 = stu; System.out.println(stu);//com.test.Student@15db9742System.out.println(stu1);//com.test.Student@15db9742
通过运行结果我们知道,stu和stu1指向同一个对象,stu和stu1是同一个对象的不同引用,也就是说这种方式复制的只是对象的引用,并没有将对象复制一份,大家都知道对象的引用是存储在栈内存中,对象本身是存储在堆空间中的,这种方式只是在栈空间中又重新开辟了一个空间存储堆内存中同一个对象的地址。
对象复制
上面的方式仅仅只能复制对象的引用,那么如果我想复制一个对象怎么办?其实这个也好办,java中已经给出了复制类的方法,我们看一个对象的方法时候总能看到一个clone()方法,刚开始学java时候还纳闷,怎么每个对象总有一个clone方法,干嘛用的,就知道这单词是克隆的意思,但是还真的不知道这个方法是干嘛用的而且每个对象都有。
那么到底怎么克隆对象呢?在java中,任何类只要实现了Cloneable接口,那么使用Object类提供的clone本地方法就可以实现对象的复制,实现Cloneable接口并不需要实现任何方法,这是一个标识接口,标识这个类可以复制,那么上述的Student类怎么进行复制呢?我们看到,上面的Student类已经实现了Cloneable接口,那么直接使用clone()方法就可以完成复制,代码如下:
publicstaticvoidmain(String[] args) throws CloneNotSupportedException{ Student stu =newStudent("zhangsan","nan",10); Student stu1 = (Student)stu.clone(); System.out.println(stu);//com.test.Student@15db9742System.out.println(stu1);//com.test.Student@6d06d69c}
通过测试结果可以看到,这两个对象已经不再相同了,表示的是同一个类的不同对象,在调用clone方法时会抛出一个异常CloneNotSupportedException,如果Student类没有实现Cloneable接口,那么在调用clone方法时就会抛出这个异常。
对象浅复制和深复制
浅复制
先看个例子,还是上面的学生类,我们再添加一个班级类,每个学生都属于一个班级,我们添加一个班级类,然后对Student类进行一个简单的修改,如下所示:
publicclassStudentimplementsCloneable{privateString ame;privateString sex;privateintage;privateClasses cla;publicStudent(String ame,String sex,intage,Classes cla){this.name = name;this.sex = sex;this.age = age;this.cla = cla; }publicObjectclone(){ Student stu =null;try{ stu = (Student)super.clone(); }catch(Exception e) { e.printStackTrace(); }returnstu; }@OverridepublicStringtoString(){return"Student [name="+ name +", sex="+ sex +", age="+ age +", cla="+ cla +"]"; } }classClasses{privateString name;publicClasses(String ame){this.name = name; } }
我们在Student类中重写了clone方法,并且添加了toString方法,然后在Test类中复制学生类的一个对象,测试代码如下所示:
publicstaticvoidmain(String[] args){ Classes cla =newClasses("1班"); Student stu =newStudent("zhangsan","nan",10,cla); Student stu1 = (Student)stu.clone(); System.out.println(stu); System.out.println(stu1); }
测试结果如下所示:
从测试结果中可以看到,不管是String类型还是int类型的变量都完全复制,并且引用类型的变量在复制前后是完全相同的,也就是将对象的引用复制了一遍(栈内存复制了,堆内存没有复制),这就是典型的浅复制,也就是说在复制过程中,仅仅复制对象本身(基本变量),但是并不复制对象包含的引用指向的对象(只复制引用),过程如下图所示(原图出自这篇文章):
(这张图截自别人的文章,虽然和我们的类对不上,但是意思是一样的,原文章的连接也给出了,大家也可以参考这篇文章,写的很好。)
浅复制有什么不好的地方吗?有,如果复制对象中的cla属性发生变化,那么原来对象的cla属性也会跟着发生变化,比如学生1在1班,复制出来的学生2也在1班,现在学生2变成2班了,学生1本应该还在1班,但是现在也变成了2班,也就是说浅复制会因为一个对象的改变而影响复制对象或者被复制对象。
深复制
上面也说到了浅复制带来的问题,那么怎么解决这个问题呢?很简单,用深复制,深复制其实也很简单,就是被复制对象中的引用对象也要实现Cloneable接口,也就是说我们例子中的班级类也要实现Cloneable接口,修改后的班级类如下所示:
classClassesimplementsCloneable{privateString ame;publicClasses(String name){this.name = name; }publicStringgetName(){returnname; }publicObjectclone(){ Classes cla =null;try{ cla = (Classes)super.clone(); }catch(CloneNotSupportedException e) { e.printStackTrace(); }returncla; } }
修改Student类中的clone方法如下所示:
publicObjectclone(){ Student stu =null;try{ stu = (Student)super.clone(); stu.cla = (Classes)cla.clone();//修改点}catch(Exceptione) { e.printStackTrace(); }returnstu; }
测试类不变,这次的测试结果如下所示:
看我用红色笔标出的地方,这次的班级对象已经不同了,也就是说在复制学生对象的同时也复制了班级对象,并且复制对象的班级对象中的属性改变,并不会影响被复制对象中的班级对象的属性,这就解决了浅复制中出现的问题。
完全深复制
上面的深复制并不是完全的深复制,怎么说呢?现在假设班级对象中还引用了一个类对象,然后引用的类对象中还有一个类对象,然后一直引用下去,这时候你的深复制怎么搞呢?其实也简单,不管有多少层引用,每一层都实现Cloneable接口就可以了,这样也不是不可以,但是只能说很麻烦,一旦中间漏掉一个半个的,那整个引用链中间的复制关系就断了,其实有两种办法可以解决:
方法一:继承实现Cloneable接口的基类
这种方法针对引用链不是特别深的,可以创建一个基类实现Cloneable接口,然后被复制类中的引用链中的类都继承自这个类就可以了。
方法二:将对象序列化之后再反序列化
要将对象序列化那么对应的对象类必须实现Serializable接口,这同样是一个标识接口,要使用序列化的方式达到深复制,必须是被复制对象本身以及其中的引用都必须是可序列化的,本文中的Student和Classes类都必须实现序列化接口,然后在学生类中添加序列化复制的方法如下所示:
publicObjectserializClone(){ Student stu =null;try{//将对象写到流里ByteArrayOutputStream bo=newByteArrayOutputStream(); ObjectOutputStream oo=newObjectOutputStream(bo); oo.writeObject(this);//从流里读出来ByteArrayInputStream bi=newByteArrayInputStream(bo.toByteArray()); ObjectInputStream oi=newObjectInputStream(bi); stu = (Student)oi.readObject(); }catch(Exception e) { e.printStackTrace(); }returnstu; }
在测试类中调用这个方法测试即可,结果也是深复制,我就不再贴代码和结果了。
原型模式
可能有的朋友早就要开骂了,MDZZ,你不是要说原型模式吗,啰嗦了半天还是什么这个复制那个复制,跟原型模式有个毛线关系,这个,兄弟别着急,说完了上面的复制关系,原型模式已经说了一大半了,下面正式开喷,前面只是走个程序。
定义
用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。
看到木有,这里要用到拷贝的方式创建新的对象,上面的拷贝知识你掌握了是不是就掌握了一半的原型模式了。
原型模式怎么理解呢?其实就是根据某个类已有的实例,使用拷贝的方式创建一个新的对象。我们以前创建对象怎么创建的呢?使用new的方式,程序走到new的时候先看后面的类型,然后根据类型在内存中开辟空间,然后初始化对象中的变量,然后发布引用,这个过程还是比较多的,但是现在我们又学会了一种创建对象的方式,直接拷贝,这种方式也是在内存中拷贝一份和原来对象一样大小的内存空间,然后把原来对象中的各个变量的值拷贝给新的对象,完成之后返回一个新的对象,并且把新的对象的引用发送给外部,这种方式是不走构造方法的。
实现
在java中怎么使用原型模式呢?其实很简单,只要被复制的类实现了Cloneable接口,那么就可以调用Object类当中提供的clone()方法,然后选择深度拷贝还是浅拷贝而已,原型模式就是这么简单。