爲什麼需要克隆:
在實際編程過程中,我們常常要遇到這種情況:有一個對象A,在某一時刻A中已經包含了一些有效值,此時可能會需要一個和A完全相同新對象B,並且此後對B任何改動都不會影響到A中的值,也就是說,A與B是兩個獨立的對象,但B的初始值是由A對象確定的。在Java語言中,用簡單的賦值語句是不能滿足這種需求的,要滿足這種需求有很多途徑。
克隆的實現方式
一、淺度克隆
對於要克隆的對象,對於其基本數據類型的屬性,複製一份給新產生的對象,對於非基本數據類型的屬性,僅僅複製一份引用給新產生的對象,即新產生的對象和原始對象中的非基本數據類型的屬性都指向的是同一個對象
1、實現java.lang.Cloneable接口
要clone的類爲什麼還要實現Cloneable接口呢?Cloneable接口是一個標識接口,不包含任何方法的!這個標識僅僅是針對Object類中clone()方法的,如果clone類沒有實現Cloneable接口,並調用了Object的 clone()方法(也就是調用了super.Clone()方法),那麼Object的clone()方法就會拋出 CloneNotSupportedException異常。
2、重寫java.lang.Object.clone()方法
JDK API的說明文檔解釋這個方法將返回Object對象的一個拷貝。要說明的有兩點:一是拷貝對象返回的是一個新對象,而不是一個引用。二是拷貝對象與用new操作符返回的新對象的區別就是這個拷貝已經包含了一些原來對象的信息,而不是對象的初始信息。
觀察一下Object類的clone()方法是一個native方法,native方法的效率一般來說都是遠高於java中的非native方法。這也解釋了爲什麼要用Object中clone()方法而不是先new一個類,然後把原始對象中的信息賦到新對象中,雖然這也實現了clone功能。Object類中的clone()還是一個protected屬性的方法,重寫之後要把clone()方法的屬性設置爲public。
Object類中clone()方法產生的效果是:先在內存中開闢一塊和原始對象一樣的空間,然後原樣拷貝原始對象中的內容。對基本數據類型,這樣的操作是沒有問題的,但對非基本類型變量,我們知道它們保存的僅僅是對象的引用,這也導致clone後的非基本類型變量和原始對象中相應的變量指向的是同一個對象。
- public class Product implements Cloneable {
- private String name;
- public Object clone() {
- try {
- return super.clone();
- } catch (CloneNotSupportedException e) {
- return null;
- }
- }
- }
二、深度克隆
在淺度克隆的基礎上,對於要克隆的對象中的非基本數據類型的屬性對應的類,也實現克隆,這樣對於非基本數據類型的屬性,複製的不是一份引用,即新產生的對象和原始對象中的非基本數據類型的屬性指向的不是同一個對象
要克隆的類和類中所有非基本數據類型的屬性對應的類
1、都實現java.lang.Cloneable接口
2、都重寫java.lang.Object.clone()方法
- public class Attribute implements Cloneable {
- private String no;
- public Object clone() {
- try {
- return super.clone();
- } catch (CloneNotSupportedException e) {
- return null;
- }
- }
- }
- public class Product implements Cloneable {
- private String name;
- private Attribute attribute;
- public Object clone() {
- try {
- return super.clone();
- } catch (CloneNotSupportedException e) {
- return null;
- }
- }
- }
三、使用對象序列化和反序列化實現深度克隆
所謂對象序列化就是將對象的狀態轉換成字節流,以後可以通過這些值再生成相同狀態的對象。
對象的序列化還有另一個容易被大家忽略的功能就是對象複製(Clone),Java中通過Clone機制可以複製大部分的對象,但是衆所周知,Clone有深度Clone和淺度Clone,如果你的對象非常非常複雜,並且想實現深層 Clone,如果使用序列化,不會超過10行代碼就可以解決。
雖然Java的序列化非常簡單、強大,但是要用好,還有很多地方需要注意。比如曾經序列化了一個對象,可由於某種原因,該類做了一點點改動,然後重新被編譯,那麼這時反序列化剛纔的對象,將會出現異常。 你可以通過添加serialVersionUID屬性來解決這個問題。如果你的類是個單例(Singleton)類,是否允許用戶通過序列化機制複製該類,如果不允許你需要謹慎對待該類的實現。
- public class Attribute {
- private String no;
- }
- public class Product {
- private String name;
- private Attribute attribute;
- public Product clone() {
- ByteArrayOutputStream byteOut = null;
- ObjectOutputStream objOut = null;
- ByteArrayInputStream byteIn = null;
- ObjectInputStream objIn = null;
- try {
- byteOut = new ByteArrayOutputStream();
- objOut = new ObjectOutputStream(byteOut);
- objOut.writeObject(prototype);
- byteIn = new ByteArrayInputStream(byteOut.toByteArray());
- objIn = new ObjectInputStream(byteIn);
- return (ContretePrototype) objIn.readObject();
- } catch (IOException e) {
- throw new RuntimeException("Clone Object failed in IO.",e);
- } catch (ClassNotFoundException e) {
- throw new RuntimeException("Class not found.",e);
- } finally{
- try{
- byteIn = null;
- byteOut = null;
- if(objOut != null) objOut.close();
- if(objIn != null) objIn.close();
- }catch(IOException e){
- }
- }
- }
- }