天天看點

Java對象的深克隆與淺克隆(對象複制)(上)怎樣才能正确複制一個對象?為什麼克隆如何實作克隆

想複制一個簡單變量,很簡單:

int apples = 5;  
int pears = apples;      

基本資料類型都适用。

但若複制的是個對象,就有點複雜了。

先看段代碼:

@Data
class Student {  
   private int number;       
}

public class Test {  
     
   public static void main(String args[]) {  
       Student stu1 = new Student();  
       stu1.setNumber(12345);  
       Student stu2 = stu1;  
         
       System.out.println("學生1:" + stu1.getNumber());  
       System.out.println("學生2:" + stu2.getNumber());  
   }  
}      

結果:

學生1:12345  
學生2:12345      

對象複制不過如此,真就這麼簡單?

改變stu2執行個體的number字段:

stu2.setNumber(54321);  
System.out.println("學生1:" + stu1.getNumber());  
System.out.println("學生2:" + stu2.getNumber());      
學生1:54321  
學生2:54321      

改變學生2的學号,學生1的學号怎麼也發生變化呢?

原因出在(stu2 = stu1) 這一句。該語句的作用是将stu1的引用指派給stu2,這樣,stu1和stu2指向記憶體堆中同一個對象。

怎樣才能正确複制一個對象?

Object有11個方法,兩個protected方法,其中一個為clone:

Java對象的深克隆與淺克隆(對象複制)(上)怎樣才能正确複制一個對象?為什麼克隆如何實作克隆

第一次聲明保證克隆對象将有單獨的記憶體位址配置設定。

第二次聲明表明,原始和克隆的對象應該具有相同的類類型,但它不是強制性的。

第三聲明表明,原始和克隆的對象應該是平等的equals()方法使用,但它不是強制性的。

因為每個類最終父類都是Object,是以它們都有clone(),但該方法是protected,是以都不能在類外進行通路。

要想對一個對象進行複制,就需重寫clone()。

為什麼克隆

直接new一個新的不行嗎?

克隆的對象可能包含一些已經修改過的屬性,而new出來的對象的屬性都還是初始化時候的值,是以當需要一個新的對象來儲存目前對象的“狀态”就靠clone。

那我把這個對象的臨時屬性一個一個的指派給我新new的對象不也行嘛。是可以,但是

麻煩

而clone是個native方法,快

常見的

Object a = new Object();
Object b;
b = a;      

這種形式的代碼複制的是引用,即對象在記憶體中的位址。

而通過clone方法指派的對象跟原來的對象時同時獨立存在。

如何實作克隆

淺、深克隆的主要差別在于是否支援引用類型的成員變量的複制。

淺克隆

被複制的類需要實作Clonenable接口(不實作的話在調用clone方法會抛CloneNotSupportedException), 該接口為标記接口(不含任何方法)

覆寫clone()方法,通路修飾符設為public。方法中調用super.clone()方法得到需要的複制對象

對上面那個方法進行改造:

@Data
class Student implements Cloneable {
  
   private int number;  
     
   @Override  
   public Object clone() {  
       Student stu = null;  
       try {  
           stu = (Student)super.clone();  
       } catch(CloneNotSupportedException e) {  
           e.printStackTrace();  
       }  
       return stu;  
   }  
}

public class Test {  
   public static void main(String args[]) {  
       Student stu1 = new Student();  
       stu1.setNumber(12345);  
       Student stu2 = (Student)stu1.clone();  
         
       System.out.println("學生1:" + stu1.getNumber());  
       System.out.println("學生2:" + stu2.getNumber());  
         
       stu2.setNumber(54321);  
     
       System.out.println("學生1:" + stu1.getNumber());  
       System.out.println("學生2:" + stu2.getNumber());  
   }  
}      
學生1:12345  
學生2:12345  
學生1:12345  
學生2:54321      

如果不相信這兩個對象不是同一個對象,那麼你可以看看這一句:

System.out.println(stu1 == stu2); // false      

深克隆

在學生類裡再加一個Address類

@Data
class Address  {  
   private String add;  
}  

@Data 
class Student implements Cloneable {  
   private int number;  
 
   private Address addr;  
     
   @Override  
   public Object clone() {  
       Student stu = null;  
       try{  
           stu = (Student)super.clone();  
       }catch(CloneNotSupportedException e) {  
           e.printStackTrace();  
       }  
       return stu;  
   }  
}

public class Test {  
     
   public static void main(String args[]) {  
         
       Address addr = new Address();  
       addr.setAdd("杭州市");  
       Student stu1 = new Student();  
       stu1.setNumber(123);  
       stu1.setAddr(addr);  
         
       Student stu2 = (Student)stu1.clone();  
         
       System.out.println("學生1:" + stu1.getNumber() + ",位址:" + stu1.getAddr().getAdd());  
       System.out.println("學生2:" + stu2.getNumber() + ",位址:" + stu2.getAddr().getAdd());  
   }  
}      
學生1:123,位址:杭州市  
學生2:123,位址:杭州市      

在main方法中試着改變addr執行個體的位址。

addr.setAdd("西湖區");  
System.out.println("學生1:" + stu1.getNumber() + ",位址:" + stu1.getAddr().getAdd());  
System.out.println("學生2:" + stu2.getNumber() + ",位址:" + stu2.getAddr().getAdd());
結果:
學生1:123,位址:杭州市  
學生2:123,位址:杭州市  
學生1:123,位址:西湖區  
學生2:123,位址:西湖區      

怎麼兩個學生的位址都改變了?

淺克隆隻是複制了addr變量的引用,并沒有真正的開辟另一塊空間,将值複制後再将引用傳回給新對象。

達到真複制對象,不是純粹引用複制。要将Address類可複制化,且修改clone方法:

@Data
class Address implements Cloneable {  
   private String add;  

   @Override  
   public Object clone() {  
       Address addr = null;  
       try{  
           addr = (Address)super.clone();  
       }catch(CloneNotSupportedException e) {  
           e.printStackTrace();  
       }  
       return addr;  
   }  
}  
 
@Data
class Student implements Cloneable{  
   private int number;  
 
   private Address addr;  

   @Override  
   public Object clone() {  
       Student stu = null;  
       try{  
           stu = (Student)super.clone(); // 淺複制  
       }catch(CloneNotSupportedException e) {  
           e.printStackTrace();  
       }  
       stu.addr = (Address)addr.clone(); // 深複制  
       return stu;  
   }  
}

public class Test {  
     
   public static void main(String args[]) {  
         
       Address addr = new Address();  
       addr.setAdd("杭州市");  
       Student stu1 = new Student();  
       stu1.setNumber(123);  
       stu1.setAddr(addr);  
         
       Student stu2 = (Student)stu1.clone();  
         
       System.out.println("學生1:" + stu1.getNumber() + ",位址:" + stu1.getAddr().getAdd());  
       System.out.println("學生2:" + stu2.getNumber() + ",位址:" + stu2.getAddr().getAdd());  
         
       addr.setAdd("西湖區");  
         
       System.out.println("學生1:" + stu1.getNumber() + ",位址:" + stu1.getAddr().getAdd());  
       System.out.println("學生2:" + stu2.getNumber() + ",位址:" + stu2.getAddr().getAdd());  
   }  
}

結果:
學生1:123,位址:杭州市  
學生2:123,位址:杭州市  
學生1:123,位址:西湖區  
學生2:123,位址:杭州市      

最後我們可以看看API裡其中一個實作了clone方法的類:

Java對象的深克隆與淺克隆(對象複制)(上)怎樣才能正确複制一個對象?為什麼克隆如何實作克隆

該類其實也屬于深克隆。