谈谈 Java 的克隆
轉載自??談談 Java 的克隆
為什么要克隆對象
做開發很少用到克隆的。我能想得到的是用于調用方法時作為參數傳遞,為了保證方法調用前后對象的內部結構不被破壞,可以克隆一個對象作為參數傳遞。
使類具有克隆能力
有人可能注意到 Object 類中有一個 native 方法clone
protected native Object clone() throws CloneNotSupportedException;訪問修飾符是 protected,缺省的情況下Object 及其子類對象無法在別的類中訪問 clone(),等同于所有類缺省沒有克隆能力。
要具備克隆能力,必須實現 Cloneable 接口:
public interface Cloneable { }奇怪的是,這個接口是空的。然而不用想那么多,這只是個標記而已,同為標記接口的還有 java.io.Serializable 等。
Cloneable 存在有兩個理由:
出于安全考慮,不想讓所有的類都具有克隆能力,要求若想克隆必須實現此接口;
某個引用向上轉型為基類后,你就不知道它是否能克隆,此時可以使用 instanceof 關鍵字檢查該引用是否指向一個可克隆的對象。
要具備克隆能力,必須重寫父類的 clone() 方法,同時將訪問修飾符改為 public,必須使用 super.clone() 進行(淺)克隆。
super.clone() 做了什么
Object 中的 clone() 識別你要復制的是哪一個對象,然后為此對象分配空間,并進行對象的復制,將原始對象的內容一一復制到新對象的存儲空間中。
需要注意的是這里的復制是淺層復制(淺層克隆 shadow clone),下面舉一個淺層復制的例子:
public class Student 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;}@Overridepublic String toString() {return "Student{" +"age=" + age +", name='" + name + '\'' +", teacher=" + ((getTeacher() == null)?"未知":getTeacher().getName()) +'}';}public Object clone(){try {return super.clone();} catch (CloneNotSupportedException e) {return null;}}} public class Teacher {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;} } public class CloneTest {public static void main(String[] args){Student s1 = new Student();s1.setAge(20);s1.setName("xiaoming");Teacher teacher = new Teacher();teacher.setName("wang");s1.setTeacher(teacher);System.out.println(s1);Student s2 = (Student) s1.clone();System.out.println(s2);s1.setAge(30);s1.setName("xiaohong");teacher.setName("li");System.out.println(s1);System.out.println(s2);}}輸出為:
Student{age=20, name='xiaoming', teacher=wang}
Student{age=20, name='xiaoming', teacher=wang}
Student{age=30, name='xiaohong', teacher=li}
Student{age=20, name='xiaoming', teacher=li}
s1.setAge(30) 和 s1.setName("xiaohong") 都沒有影響到克隆對象 s2。為什么? 這里說說我的理解。
基本數據類型或裝箱基本數據類型在方法中作為參數傳遞的時候,都是傳遞的值的拷貝,所以單從它來講已經做到了深層克隆。
String 類型你可以理解為是不可變的,一旦你做了改變(比如使用連接符做拼接),它也就變成另外一個對象了,不會影響到原對象,所以單從它來講也做到了深層克隆。
teacher.setName("li") 影響到了克隆對象 s2,所以整個學生對象的克隆是淺層克隆。想要實現深層克隆,做以下修改:
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 (CloneNotSupportedException e) {return null;}} }Student的clone()修改為:
public Object clone() {try {Student student = (Student)super.clone();student.setTeacher((Teacher)student.getTeacher().clone());return student;} catch (Exception e) {return null;} }輸出為:
Student{age=20, name='xiaoming', teacher=wang}
Student{age=20, name='xiaoming', teacher=wang}
Student{age=30, name='xiaohong', teacher=li}
Student{age=20, name='xiaoming', teacher=wang}
通過序列化進行深層拷貝
按照上面的深層克隆方法,如果類的結構不同,clone() 代碼邏輯就不同,而且還可能涉及到大量的遍歷和判斷等復雜的操作。
嫌麻煩? 試試用序列化做深層拷貝吧。將對象進行序列化后再進行反序列化,其效果相當于克隆對象。
下面改改代碼來證明這句話:
public class Student2 implements Serializable {private static final long serialVersionUID = -4890130009355939897L;private int age;private String name;private Teacher2 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 Teacher2 getTeacher() {return teacher;}public void setTeacher(Teacher2 teacher) {this.teacher = teacher;}@Overridepublic String toString() {return "Student2{" +"age=" + age +", name='" + name + '\'' +", teacher=" + ((getTeacher() == null)?"未知":getTeacher().getName()) +'}';}} public class Teacher2 implements Serializable {private String name;public String getName() {return name;}public void setName(String name) {this.name = name;}} public class CloneTest2 {public static void main(String[] args) throws Exception{Student2 s2 = new Student2();s2.setAge(20);s2.setName("xiaoming");Teacher2 teacher2 = new Teacher2();teacher2.setName("wang");s2.setTeacher(teacher2);System.out.println(s2);ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();ObjectOutputStream objOutputStream = new ObjectOutputStream(byteArrayOutputStream);objOutputStream.writeObject(s2);ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());ObjectInputStream objInputStream = new ObjectInputStream(byteArrayInputStream);Student2 s22 = (Student2) objInputStream.readObject();System.out.println(s22.toString());s2.setAge(30);s2.setName("xiaohong");teacher2.setName("li");System.out.println(s2.toString());System.out.println(s22.toString());}}輸出:
Student2{age=20, name='xiaoming', teacher=wang}
Student2{age=20, name='xiaoming', teacher=wang}
Student2{age=30, name='xiaohong', teacher=li}
Student2{age=20, name='xiaoming', teacher=wang}
幾行序列化和反序列化代碼,簡單粗暴,適合絕大多數情況,再也不用為復雜的克隆邏輯而擔憂了。
總結
以上是生活随笔為你收集整理的谈谈 Java 的克隆的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java图形验证码生成工具类
- 下一篇: 对Java的URL类支持的协议进行扩展的