序列化进行克隆
相关类的定义
//Person类的对象数据域,用来检测是否实现了深克隆classPetimplementsCloneable,Serializable{Stringtype;publicPet(Stringtype){this.type=type;}publicvoidsetType(Stringtype){this.type=type;}@OverridepublicStringtoString(){returnthis.type;}}//Person类,用来进行克隆的类classPersonimplementsCloneable,Serializable{Stringname;intage;Petpet;publicPerson(Stringname,intage,Petpet){this.name=name;this.age=age;this.pet=pet;}publicvoidsetPet(Petpet){this.pet=pet;}publicPetgetPet(){returnpet;}publicvoidsetAge(intage){this.age=age;}publicvoidsetName(Stringname){this.name=name;}@Override//用序列化实现的克隆方法publicPersonclone()throwsCloneNotSupportedException{//manylines}@OverridepublicStringtoString(){return"name:"+name+"age:"+age+"pet:"+pet;}}
首先看一下克隆相关的类的定义,定义了一个Pet类作为Person类的数据域成员,从而通过改变克隆对象的Pet数据域的值检测是否成功进行了深克隆。Pet类和Person类里都有一些set和get方法,以及重写的toString 方法。另外,两个类都实现了Serializable 接口,这同样是个标记接口,想对对象进行序列化,必须实现它。
序列化克隆方法
publicPersonclone()throwsCloneNotSupportedException{try{ByteArrayOutputStreambout=newByteArrayOutputStream();try(ObjectOutputStreamout=newObjectOutputStream(bout)){out.writeObject(this);}try(InputStreambin=newByteArrayInputStream(bout.toByteArray())){ObjectInputStreamin=newObjectInputStream(bin);return(Person)in.readObject();}}catch(IOException|ClassNotFoundExceptione){e.printStackTrace();}returnnull;}
上一节省略了clone 方法,这一节详细看看,可以看出,先是用ObjectOutputStream
流把对象写入了ByteArrayOutputStream
流里,进行了序列化,再用ObjectInputStream
流从ByteArrayOutputStream
流里进行读取,实现反序列化,这样就用序列化和反序列化的思想成功的进行了克隆。因为不涉及对象传输和持久化的问题,只是进行克隆,所以这里并没有序列化到磁盘里。
测试是否为深克隆
publicstaticvoidmain(String[]args)throwsCloneNotSupportedException{Personper=newPerson("testClone",18,newPet("cat"));PersonperClone=per.clone();//设置克隆对象的各个数据域perClone.setAge(19);perClone.setName("clone");perClone.getPet().setType("dog");//输出两个对象System.out.println("原对象:"+per);System.out.println("克隆对象:"+perClone);}
对克隆对象的各个数据域进行修改,测试结果如下:
克隆对象的修改没有影响原对象,这种方法同样完成了深克隆。
效率检测
《Java核心技术 卷2》里提到:这种序列化进行克隆的方法比之前逐个复制数据域的方法会慢很多,接下来测试一下。
publicstaticvoidmain(String[]args)throwsCloneNotSupportedException{Personper=newPerson("testClone",18,newPet("cat"));longstartTime=System.currentTimeMillis();for(inti=0;i<10000;++i){PersonperClone=per.clone();}longendTime=System.currentTimeMillis();System.out.println("运行时间:"+(endTime-startTime)+"ms");}
测试的主函数如上,进行一万次对象克隆,我们先测试序列化克隆时间:
接着把序列化克隆的 clone 方法代码注释掉,clone 方法换成以下传统方法代码:
publicPersonclone()throwsCloneNotSupportedException{Personcloned=(Person)super.clone();cloned.pet=pet.clone();returncloned;}
运行同样的程序,进行测试,结果如下:
可见,效率差了一百倍,猜想可能是输入输出流的序列化写入和读取相较于直接克隆更耗时吧。
为了验证我的猜想以及满足我自己的好奇心,我把序列化的克隆方法改写如下:
publicPersonclone()throwsCloneNotSupportedException{try{try(ObjectOutputStreamout=newObjectOutputStream(newFileOutputStream("testClone.txt"))){out.writeObject(this);}try(ObjectInputStreamin=newObjectInputStream(newFileInputStream("testClone.txt"))){return(Person)in.readObject();}}catch(IOException|ClassNotFoundExceptione){e.printStackTrace();}returnnull;}
现在不是在内存里的ByteArrayOutputStream
流里进行写入和读取了,现在把对象序列化到磁盘里的testClone.txt
文件里,再反序列化回来,运行同样的时间检测代码,结果如下:
可以发现,效率非常感人。同样都是序列化和反序列化,这种方法比借助ByteArrayOutputStream
流慢了两百倍,可见,慢就慢在磁盘的读写上了。
总结
Java的对象序列化和反序列化给我们提供了另外一种对象深克隆的方法。这种方法的优点是编写代码简单,不用把数据域的对象引用单独拎出来重写其clone方法,就可以进行深拷贝;缺点是效率比较感人。