深克隆(拷贝)与浅克隆(拷贝)
一、什么是克隆钱(拷贝)
在实际编程过程中,我们常常遇到这种情况:有一个对象A,在某一时刻A中已经包含了一些有效值,此时可能会需要一个和A完全相同的新对象B,并且此后对B做任何改动都不会影响到A中的值,也就是说,A与B是两个独立的对象,但是B的初始值是由A对象确定的。在java语言中,用简单的赋值语言是不能满足这种需求的。要满足这种需求虽然有很多途径,但实现clone()方法是其中最简单也是最高效的手段。
Java的所有类都默认继承java.lang.Object类,在java.lang.Object类中有一个方法clone()。JDK API的说明文档解释这个方法将返回Object对象的一个拷贝。要说明的有两点:一是拷贝对象返回的是一个新对象,而不是一个引用。二是拷贝对象与用 new操作符返回的新对象的区别就是这个拷贝已经包含了一些原来对象的信息,而不是对象的初始信息。
二、深拷贝与浅拷贝
浅拷贝是指拷贝对象时仅仅拷贝对象本身(包括对象中的基本变量),而不拷贝对象包含的引用指向的对象。深拷贝不仅拷贝对象本身,而且拷贝对象包含的引用指向的所有对象。举例来说更加清楚:对象A1中包含对B1的引用,B1中包含对C1的引用。浅拷贝A1得到A2,A2 中依然包含对B1的引用,B1中依然包含对C1的引用。深拷贝则是对浅拷贝的递归,深拷贝A1得到A2,A2中包含对B2(B1的copy)的引用,B2 中包含对C2(C1的copy)的引用。若不对clone()方法进行改写,则调用此方法得到的对象即为浅拷贝。
三、代码实现
1.浅克隆
package clone; import com.sun.star.i18n.Implementation; /** * 克隆测试:浅克隆,其实就是把被复制的这个对象的一些变量值拿过来 * @author coco.xu * */ public class CloneTest1 { public static void main(String[] args) throws Exception { Student student = new Student(); student.setAge(18); student.setName("coco"); Student student2 = (Student)student.clone(); //这个是调用下面的那个方法,然后把这个这个对象Clone到student System.out.println("Age:" + student2.getAge() + "&&Name:" + student2.getName()); System.out.println("---------------------"); //克隆后得到的是一个新的对象,所以重新写的是student2这个对象的值 student2.setAge(23); System.out.println(student.getAge()); System.out.println(student2.getAge()); } } /** * 克隆的对象必须实现Cloneable这个接口,而且需要重写clone方法 * @author coco.xu * */ class Student implements Cloneable { private int age; //定义为private说明这个成员变量只能被被当前类中访问,如果外部需要获得,那么就只能通过getAge方法进行获取 private String name; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public Object clone() throws CloneNotSupportedException { Object object = super.clone(); return object; } }
2.深克隆
package clone; /** * 深克隆 * @author coco.xu * */ public class CloneTest2 { public static void main(String[] args) throws Exception { Teacher teacher = new Teacher(); teacher.setAge(30); teacher.setName("coco xu"); Student2 student2 = new Student2(); student2.setAge(10); student2.setName("zhangsan"); student2.setTeacher(teacher); Student2 student3 = (Student2)student2.clone(); //这里是深复制,所以这时候Student2中的teacher就是teacher这个对象的一个复制,就和student3是student2的一个复制 //所以下面teacher.setName只是对他原来的这个对象更改,但是复制的那个并没有更改 System.out.println(student3.getAge()); System.out.println(student3.getName()); System.out.println(student3.getTeacher().getAge()); teacher.setName("teacher xixi");//不会又任何影响 System.out.println(student3.getTeacher().getName()); } } class Student2 implements Cloneable { private int age; private String name; private Teacher teacher; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Teacher getTeacher() { return teacher; } public void setTeacher(Teacher teacher) { this.teacher = teacher; } @Override public Object clone() throws CloneNotSupportedException { //这一步返回的这个student2还只是一个浅克隆, Student2 student2 = (Student2)super.clone(); //然后克隆的过程中获得这个克隆的student2,然后调用这个getTeacher这个方方法得到这个Teacher对象。然后实现克隆。在设置到这个student2中的Teacher。 //这样实现了双层克隆使得那个teacher对象也得到了复制。 student2.setTeacher((Teacher)student2.getTeacher().clone()); //双层克隆使得那个teacher对象也得到了复制 return student2; } } class Teacher implements Cloneable { private int age; private String name; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public Object clone() throws CloneNotSupportedException { return super.clone(); } }
3.利用serializable实现深克隆(这个是利用Serializable,利用序列化的方式来实现深克隆(深克隆),在其中利用了Io流的方式将这个对象写到IO流里面,然后在从IO流里面读取,这样就实现了一个克隆,然后实现序列化的这个会将引用的那个对象也一并进行深克隆,这样就实现了这个机制,同时在IO里面读取数据的时候还使用了装饰者模式)
package clone; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class CloneTest3 { public static void main(String[] args) throws Exception { Teacher3 teacher3 = new Teacher3(); teacher3.setAge(23); teacher3.setName("coco"); Student3 student3 = new Student3(); student3.setAge(50); student3.setName("xixi"); student3.setTeacher3(teacher3); Student3 ss = (Student3) student3.deepCopy(); System.out.println(ss.getAge()); System.out.println(ss.getName()); System.out.println("---------------------"); System.out.println(ss.getTeacher3().getAge()); System.out.println(ss.getTeacher3().getName()); System.out.println("-----------------------"); ss.getTeacher3().setAge(34); ss.getTeacher3().setName("hhhhh"); System.out.println(teacher3.getAge()); System.out.println(teacher3.getName()); // 虽然上面的已经改了,但是改的是那个复制对象后的那个里面的,然后那个原来的那个里面的并没有改,下面验证::: System.out.println("-----------------"); System.out.println(ss.getTeacher3().getAge()); System.out.println(ss.getTeacher3().getName()); } } class Teacher3 implements Serializable { // 上面的那个警告可以直接消除,除了使用在设置中不显示这个警告,还可以使用下面的这两条语句中的任何一条语句 // 这个serialVersionUID为了让该类别Serializable向后兼容 private static final long serialVersionUID = 8940196742313994740L; private int age; private String name; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } } class Student3 implements Serializable { private static final long serialVersionUID = 1L; private int age; private String name; private Teacher3 teacher3; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public String getName() { return name; } public void setName(String name) { this.name = name; } public Teacher3 getTeacher3() { return teacher3; } public void setTeacher3(Teacher3 teacher3) { this.teacher3 = teacher3; } // 使得序列化student3的时候也会将teacher序列化 public Object deepCopy() throws Exception { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(this); // 将当前这个对象写到一个输出流当中,,因为这个对象的类实现了Serializable这个接口,所以在这个类中 // 有一个引用,这个引用如果实现了序列化,那么这个也会写到这个输出流当中 ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); return ois.readObject(); // 这个就是将流中的东西读出类,读到一个对象流当中,这样就可以返回这两个对象的东西,实现深克隆 } }
要想序列化对象,必须先创建一个OutputStream,然后把它嵌入ObjectOutputStream。这时就能用writeObject()方法把对象写入OutputStream。读的时候需要把InputStream嵌到ObjectInputStream中,然后再调用readObject()方法。不过这样读出来的只是一个Object的reference,因此,在用之前,还要下转型。对象序列化不仅能保存对象的副本,而且会跟着对象中的reference把它所引用的对象也保存起来,然后再继续跟踪那些对象的reference,以此类推。这种情形常被称作”单个对象所联结的‘对象网’ “。
但是串行化却很耗时,在一些框架中,我们便可以感受到,它们往往将对象进行串行化后进行传递,耗时较多。