谈谈 Java的克隆
ug2847
8年前
<h2><strong>为什么要克隆对象</strong></h2> <p>做开发很少用到克隆的。我能想得到的是用于调用方法时作为参数传递,为了保证方法调用前后对象的内部结构不被破坏,可以克隆一个对象作为参数传递。</p> <h2><strong>使类具有克隆能力</strong></h2> <p>有人可能注意到 Object 类中有一个 native 方法clone</p> <pre> <code class="language-java">protected native Object clone() throws CloneNotSupportedException; </code></pre> <p>访问修饰符是 protected,缺省的情况下Object 及其子类对象无法在别的类中访问 clone(), <strong>等同于所有类缺省没有克隆能力。</strong></p> <p><strong>要具备克隆能力,必须实现 Cloneable 接口</strong>:</p> <pre> <code class="language-java">public interface Cloneable {} </code></pre> <p>奇怪的是,这个接口是空的。然而不用想那么多,这只是个标记而已,同为标记接口的还有 java.io.Serializable 等。</p> <p>Cloneable 存在有两个理由:</p> <ol> <li>出于安全考虑,不想让所有的类都具有克隆能力,要求若想克隆必须实现此接口;</li> <li>某个引用向上转型为基类后,你就不知道它是否能克隆,此时可以使用 instanceof 关键字检查该引用是否指向一个可克隆的对象。</li> </ol> <p><strong>要具备克隆能力,必须重写父类的 clone() 方法,同时将访问修饰符改为 public,必须使用 super.clone() 进行(浅)克隆。</strong></p> <h2><strong>super.clone() 做了什么</strong></h2> <p>Object 中的 clone() 识别你要复制的是哪一个对象,然后为此对象分配空间,并进行对象的复制,将原始对象的内容一一复制到新对象的存储空间中。</p> <p>需要注意的是这里的复制是浅层复制(浅层克隆 shadow clone),下面举一个浅层复制的例子:</p> <pre> <code class="language-java">public class Student implements Cloneable{ private String name; private int age; private Teacher teacher; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Teacher getTeacher() { return teacher; } public void setTeacher(Teacher teacher) { this.teacher = teacher; } @Override public String toString() { String str = "姓名:" + getName() + ",年龄:" + getAge() + ",老师:" + ((getTeacher()==null)?"未知":getTeacher().getName()); return str; } public Object clone(){ try { return super.clone(); } catch (Exception e) { return null; } } } </code></pre> <pre> <code class="language-java">public class Teacher { private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } } </code></pre> <pre> <code class="language-java">public class CloneTest1 { public static void main(String[] args) { Student s1 = new Student(); s1.setAge(10); s1.setName("Li"); Teacher teacher = new Teacher(); teacher.setName("Wu"); s1.setTeacher(teacher); System.out.println(s1.toString()); Student s2 = (Student) s1.clone(); System.out.println(s2.toString()); s1.setAge(20); s1.setName("Hu"); teacher.setName("Yang"); System.out.println(s2.toString()); } } </code></pre> <p>输出为:</p> <pre> <code class="language-java">姓名:Li,年龄:10,老师:Wu 姓名:Li,年龄:10,老师:Wu 姓名:Li,年龄:10,老师:Yang </code></pre> <p>s1.setAge(20) 和 s1.setName("Hu") 都没有影响到克隆对象 s2。为什么? 这里说说我的理解</p> <p>基本数据类型或装箱基本数据类型在方法中作为参数传递的时候,都是传递的值得拷贝,所以单从它来讲已经做到了深层克隆。</p> <p>String 类型你可以理解为是不可变的,一旦你做了改变(比如使用连接符做拼接),它也就变成另外一个对象了,不会影响到原对象,所以单从它来讲也做到了深层克隆。</p> <p>teacher.setName("Yang") 影响到了克隆对象 s2,所以整个学生对象的克隆是浅层克隆。想要实现深层克隆,做以下修改</p> <pre> <code class="language-java">public class Teacher implements Cloneable{ private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } public Object clone(){ try { return super.clone(); } catch (Exception e) { return null; } } } </code></pre> <pre> <code class="language-java">public class Student implements Cloneable{ private String name; private int age; private Teacher teacher; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Teacher getTeacher() { return teacher; } public void setTeacher(Teacher teacher) { this.teacher = teacher; } @Override public String toString() { String str = "姓名:" + getName() + ",年龄:" + getAge() + ",老师:" + ((getTeacher()==null)?"未知":getTeacher().getName()); return str; } public Object clone(){ try { Student stu = (Student) super.clone(); stu.setTeacher((Teacher)stu.getTeacher().clone()); return stu; } catch (Exception e) { return null; } } } </code></pre> <p>输出为:</p> <pre> <code class="language-java">姓名:Li,年龄:10,老师:Wu 姓名:Li,年龄:10,老师:Wu 姓名:Li,年龄:10,老师:Wu </code></pre> <h2><strong>通过序列化进行深层拷贝</strong></h2> <p>按照上面的深层克隆方法,如果类的结构不同,clone() 代码逻辑就不同,而且还可能涉及到大量的遍历和判断等复杂的操作。</p> <p>嫌麻烦? 试试用序列化做深层拷贝吧。<strong>将对象进行序列化后再进行反序列化,其效果相当于克隆对象。</strong></p> <p>下面改改代码来证明这句话:</p> <p> </p> <pre> <code class="language-java">public class Student implements Serializable{ private String name; private int age; private Teacher teacher; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Teacher getTeacher() { return teacher; } public void setTeacher(Teacher teacher) { this.teacher = teacher; } @Override public String toString() { String str = "姓名:" + getName() + ",年龄:" + getAge() + ",老师:" + ((getTeacher()==null)?"未知":getTeacher().getName()); return str; } }</code></pre> <pre> <code class="language-java">public class Teacher implements Serializable{ private String name; public String getName() { return name; } public void setName(String name) { this.name = name; } }</code></pre> <p> </p> <pre> <code class="language-java">public class CloneTest1 { public static void main(String[] args) throws Exception{ Student s1 = new Student(); s1.setAge(10); s1.setName("Li"); Teacher teacher = new Teacher(); teacher.setName("Wu"); s1.setTeacher(teacher); System.out.println(s1.toString()); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); ObjectOutputStream objOutputStream = new ObjectOutputStream(byteArrayOutputStream); objOutputStream.writeObject(s1); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray()); ObjectInputStream objInputStream = new ObjectInputStream(byteArrayInputStream); Student s2 = (Student) objInputStream.readObject(); System.out.println(s2.toString()); s1.setAge(20); s1.setName("Hu"); teacher.setName("Yang"); System.out.println(s2.toString()); } }</code></pre> <p> </p> <p>输出:</p> <pre> <code class="language-java">姓名:Li,年龄:10,老师:Wu 姓名:Li,年龄:10,老师:Wu 姓名:Li,年龄:10,老师:Wu</code></pre> <p>几行序列化和反序列化代码,简单粗暴,适合绝大多数情况,再也不用为复杂的克隆逻辑而担忧了。</p> <p> </p> <p> </p> <p>来自:http://www.cnblogs.com/xmsx/p/5852473.html</p> <p> </p>