日韩无码专区无码一级三级片|91人人爱网站中日韩无码电影|厨房大战丰满熟妇|AV高清无码在线免费观看|另类AV日韩少妇熟女|中文日本大黄一级黄色片|色情在线视频免费|亚洲成人特黄a片|黄片wwwav色图欧美|欧亚乱色一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營銷解決方案
Java提高篇——對象克?。◤?fù)制)

假如說你想復(fù)制一個(gè)簡單變量。很簡單:

靈川網(wǎng)站建設(shè)公司創(chuàng)新互聯(lián),靈川網(wǎng)站設(shè)計(jì)制作,有大型網(wǎng)站制作公司豐富經(jīng)驗(yàn)。已為靈川數(shù)千家提供企業(yè)網(wǎng)站建設(shè)服務(wù)。企業(yè)網(wǎng)站搭建\成都外貿(mào)網(wǎng)站制作要多少錢,請找那個(gè)售后服務(wù)好的靈川做網(wǎng)站的公司定做!

 
 
 
 
  1. int apples = 5;    
  2. int pears = apples; 

不僅僅是int類型,其它七種原始數(shù)據(jù)類型(boolean,char,byte,short,float,double.long)同樣適用于該類情況。

但是如果你復(fù)制的是一個(gè)對象,情況就有些復(fù)雜了。

假設(shè)說我是一個(gè)beginner,我會(huì)這樣寫:

 
 
 
 
  1. class Student {    
  2.    private int number;     
  3.    public int getNumber() {  
  4.        return number;    
  5.    }  
  6.  
  7.    public void setNumber(int number) {    
  8.        this.number = number;    
  9.    }   
  10. }    
  11. public class Test {    
  12.    public static void main(String args[]) {   
  13.        Student stu1 = new Student();    
  14.        stu1.setNumber(12345);    
  15.        Student stu2 = stu1;             
  16.  
  17.        System.out.println("學(xué)生1:" + stu1.getNumber());    
  18.        System.out.println("學(xué)生2:" + stu2.getNumber());   
  19.    }    

結(jié)果:

 
 
 
 
  1. 學(xué)生1:12345   
  2. 學(xué)生2:12345 

這里我們自定義了一個(gè)學(xué)生類,該類只有一個(gè)number字段。

我們新建了一個(gè)學(xué)生實(shí)例,然后將該值賦值給stu2實(shí)例。(Student stu2 = stu1;)

再看看打印結(jié)果,作為一個(gè)新手,拍了拍胸腹,對象復(fù)制不過如此!

難道真的是這樣嗎?

我們試著改變stu2實(shí)例的number字段,再打印結(jié)果看看:

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

結(jié)果:

 
 
 
 
  1. 學(xué)生1:54321    
  2. 學(xué)生2:54321 

這就怪了,為什么改變學(xué)生2的學(xué)號(hào),學(xué)生1的學(xué)號(hào)也發(fā)生了變化呢?

原因出在(stu2 = stu1) 這一句。該語句的作用是將stu1的引用賦值給stu2,這樣,stu1和stu2指向內(nèi)存堆中同一個(gè)對象。如圖:

那么,怎樣才能達(dá)到復(fù)制一個(gè)對象呢?

是否記得萬類之王Object。它有11個(gè)方法,有兩個(gè)protected的方法,其中一個(gè)為clone方法。

在Java中所有的類都是缺省的繼承自Java語言包中的Object類的,查看它的源碼,你可以把你的JDK目錄下的src.zip復(fù)制到其他地方然后解壓,里面就是所有的源碼。發(fā)現(xiàn)里面有一個(gè)訪問限定符為protected的方法clone():

 
 
 
 
  1. /*  
  2. Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object.  
  3. The general intent is that, for any object x, the expression:  
  4. 1) x.clone() != x will be true  
  5. 2) x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements.  
  6. 3) x.clone().equals(x) will be true, this is not an absolute requirement.  
  7. */  
  8. protected native Object clone() throws CloneNotSupportedException; 

仔細(xì)一看,它還是一個(gè)native方法,大家都知道native方法是非Java語言實(shí)現(xiàn)的代碼,供Java程序調(diào)用的,因?yàn)镴ava程序是運(yùn)行在JVM虛擬機(jī)上面的,要想訪問到比較底層的與操作系統(tǒng)相關(guān)的就沒辦法了,只能由靠近操作系統(tǒng)的語言來實(shí)現(xiàn)。

第一次聲明保證克隆對象將有單獨(dú)的內(nèi)存地址分配。

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

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

因?yàn)槊總€(gè)類直接或間接的父類都是Object,因此它們都含有clone()方法,但是因?yàn)樵摲椒ㄊ莗rotected,所以都不能在類外進(jìn)行訪問。

要想對一個(gè)對象進(jìn)行復(fù)制,就需要對clone方法覆蓋。

為什么要克隆?

大家先思考一個(gè)問題,為什么需要克隆對象?直接new一個(gè)對象不行嗎?

答案是:克隆的對象可能包含一些已經(jīng)修改過的屬性,而new出來的對象的屬性都還是初始化時(shí)候的值,所以當(dāng)需要一個(gè)新的對象來保存當(dāng)前對象的“狀態(tài)”就靠clone方法了。那么我把這個(gè)對象的臨時(shí)屬性一個(gè)一個(gè)的賦值給我新new的對象不也行嘛?可以是可以,但是一來麻煩不說,二來,大家通過上面的源碼都發(fā)現(xiàn)了clone是一個(gè)native方法,就是快啊,在底層實(shí)現(xiàn)的。

提個(gè)醒,我們常見的Object a=new Object();Object b;b=a;這種形式的代碼復(fù)制的是引用,即對象在內(nèi)存中的地址,a和b對象仍然指向了同一個(gè)對象。

而通過clone方法賦值的對象跟原來的對象時(shí)同時(shí)獨(dú)立存在的。

如何實(shí)現(xiàn)克隆

先介紹一下兩種不同的克隆方法,淺克隆(ShallowClone)和深克隆(DeepClone)。

在Java語言中,數(shù)據(jù)類型分為值類型(基本數(shù)據(jù)類型)和引用類型,值類型包括int、double、byte、boolean、char等簡單數(shù)據(jù)類型,引用類型包括類、接口、數(shù)組等復(fù)雜類型。淺克隆和深克隆的主要區(qū)別在于是否支持引用類型的成員變量的復(fù)制,下面將對兩者進(jìn)行詳細(xì)介紹。

一般步驟是(淺克?。?/p>

1. 被復(fù)制的類需要實(shí)現(xiàn)Clonenable接口(不實(shí)現(xiàn)的話在調(diào)用clone方法會(huì)拋出CloneNotSupportedException異常), 該接口為標(biāo)記接口(不含任何方法)

2. 覆蓋clone()方法,訪問修飾符設(shè)為public。方法中調(diào)用super.clone()方法得到需要的復(fù)制對象。(native為本地方法)

下面對上面那個(gè)方法進(jìn)行改造:

 
 
 
 
  1. class Student implements Cloneable{   
  2.    private int number;     
  3.    public int getNumber() {   
  4.        return number;    
  5.    }     
  6.    public void setNumber(int number) {    
  7.        this.number = number;    
  8.    }   
  9.  
  10.    @Override    
  11.    public Object clone() {  
  12.        Student stu = null;    
  13.        try{    
  14.            stu = (Student)super.clone();    
  15.        }catch(CloneNotSupportedException e) {    
  16.            e.printStackTrace();   
  17.        }    
  18.        return stu;    
  19.    }    
  20. }    
  21. public class Test {  
  22.    public static void main(String args[]) {   
  23.        Student stu1 = new Student();   
  24.        stu1.setNumber(12345);    
  25.        Student stu2 = (Student)stu1.clone();             
  26.  
  27.        System.out.println("學(xué)生1:" + stu1.getNumber());  
  28.        System.out.println("學(xué)生2:" + stu2.getNumber());   
  29.           
  30.        stu2.setNumber(54321);   
  31.  
  32.        System.out.println("學(xué)生1:" + stu1.getNumber());    
  33.        System.out.println("學(xué)生2:" + stu2.getNumber());   
  34.    }    

結(jié)果:

 
 
 
 
  1. 學(xué)生1:12345  
  2. 學(xué)生2:12345   
  3. 學(xué)生1:12345   
  4. 學(xué)生2:54321 

如果你還不相信這兩個(gè)對象不是同一個(gè)對象,那么你可以看看這一句:

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

上面的復(fù)制被稱為淺克隆。

還有一種稍微復(fù)雜的深度復(fù)制:

我們在學(xué)生類里再加一個(gè)Address類。

 
 
 
 
  1. class Address  {   
  2.    private String add;    
  3.    public String getAdd() {  
  4.        return add;   
  5.    }   
  6.  
  7.    public void setAdd(String add) {    
  8.        this.add = add;   
  9.    }    
  10. }   
  11.  
  12. class Student implements Cloneable{   
  13.    private int number;    
  14.    private Address addr;       
  15.  
  16.    public Address getAddr() {    
  17.        return addr;    
  18.    }   
  19.  
  20.    public void setAddr(Address addr) {    
  21.        this.addr = addr;  
  22.    }   
  23.  
  24.    public int getNumber() {    
  25.        return number;    
  26.    }     
  27.  
  28.    public void setNumber(int number) {    
  29.        this.number = number;   
  30.    }    
  31.  
  32.    @Override    
  33.    public Object clone() {    
  34.        Student stu = null;    
  35.        try{    
  36.            stu = (Student)super.clone();    
  37.        }catch(CloneNotSupportedException e) {   
  38.            e.printStackTrace();    
  39.        }    
  40.        return stu;    
  41.    }    
  42. }    
  43. public class Test {    
  44.    public static void main(String args[]) {    
  45.        Address addr = new Address();    
  46.        addr.setAdd("杭州市");    
  47.        Student stu1 = new Student();   
  48.        stu1.setNumber(123);    
  49.        stu1.setAddr(addr);           
  50.  
  51.        Student stu2 = (Student)stu1.clone(); 
  52.        System.out.println("學(xué)生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());   
  53.        System.out.println("學(xué)生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());   
  54.    }   

結(jié)果:

 
 
 
 
  1. 學(xué)生1:123,地址:杭州市    
  2. 學(xué)生2:123,地址:杭州市 

乍一看沒什么問題,真的是這樣嗎?

我們在main方法中試著改變addr實(shí)例的地址。

 
 
 
 
  1. addr.setAdd("西湖區(qū)");     
  2. System.out.println("學(xué)生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());    
  3. System.out.println("學(xué)生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd()); 

結(jié)果:

 
 
 
 
  1. 學(xué)生1:123,地址:杭州市    
  2. 學(xué)生2:123,地址:杭州市  
  3. 學(xué)生1:123,地址:西湖區(qū)   
  4. 學(xué)生2:123,地址:西湖區(qū) 

這就奇怪了,怎么兩個(gè)學(xué)生的地址都改變了?

原因是淺復(fù)制只是復(fù)制了addr變量的引用,并沒有真正的開辟另一塊空間,將值復(fù)制后再將引用返回給新對象。

所以,為了達(dá)到真正的復(fù)制對象,而不是純粹引用復(fù)制。我們需要將Address類可復(fù)制化,并且修改clone方法,完整代碼如下:

 
 
 
 
  1. package abc;    
  2. class Address implements Cloneable {  
  3.    private String add;    
  4.    public String getAdd() {   
  5.        return add;    
  6.    }     
  7.    public void setAdd(String add) {    
  8.        this.add = add;    
  9.    }     
  10.  
  11.    @Override   
  12.    public Object clone() {   
  13.        Address addr = null;    
  14.        try{    
  15.            addr = (Address)super.clone();   
  16.        }catch(CloneNotSupportedException e) {    
  17.            e.printStackTrace();    
  18.        }    
  19.        return addr;    
  20.    }    
  21. }     
  22.  
  23. class Student implements Cloneable{    
  24.    private int number;     
  25.    private Address addr;         
  26.  
  27.    public Address getAddr() {  
  28.        return addr;    
  29.    }   
  30.  
  31.    public void setAddr(Address addr) {    
  32.        this.addr = addr;   
  33.    }   
  34.  
  35.    public int getNumber() {    
  36.        return number;    
  37.    }   
  38.    public void setNumber(int number) {    
  39.        this.number = number;   
  40.    }   
  41.  
  42.    @Override    
  43.    public Object clone() {    
  44.        Student stu = null;    
  45.        try{    
  46.            stu = (Student)super.clone();   //淺復(fù)制    
  47.        }catch(CloneNotSupportedException e) {    
  48.            e.printStackTrace();    
  49.        }    
  50.        stu.addr = (Address)addr.clone();   //深度復(fù)制    
  51.        return stu;    
  52.    }    
  53. }    
  54. public class Test {   
  55.     public static void main(String args[]) {   
  56.        Address addr = new Address();    
  57.        addr.setAdd("杭州市");   
  58.        Student stu1 = new Student();   
  59.        stu1.setNumber(123);   
  60.        stu1.setAddr(addr);    
  61.        Student stu2 = (Student)stu1.clone();   
  62.  
  63.        System.out.println("學(xué)生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());  
  64.        System.out.println("學(xué)生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());   
  65.  
  66.        addr.setAdd("西湖區(qū)");    
  67.        System.out.println("學(xué)生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());  
  68.        System.out.println("學(xué)生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());    
  69.    }    

結(jié)果:

 
 
 
 
  1. 學(xué)生1:123,地址:杭州市    
  2. 學(xué)生2:123,地址:杭州市   
  3. 學(xué)生1:123,地址:西湖區(qū)  
  4. 學(xué)生2:123,地址:杭州市 

這樣結(jié)果就符合我們的想法了。

最后我們可以看看API里其中一個(gè)實(shí)現(xiàn)了clone方法的類:

 
 
 
 
  1. java.util.Date:  
  2. /**   
  3. * Return a copy of this object.   
  4. */   
  5.  public Object clone() {    
  6.    Date d = null;    
  7.    try {    
  8.        d = (Date)super.clone();  
  9.        if (cdate != null) {    
  10.            d.cdate = (BaseCalendar.Date) cdate.clone();   
  11.        }    
  12.    } catch (CloneNotSupportedException e) {} // Won't happen    
  13.    return d;   

該類其實(shí)也屬于深度復(fù)制。

淺克隆和深克隆

1、淺克隆

在淺克隆中,如果原型對象的成員變量是值類型,將復(fù)制一份給克隆對象;如果原型對象的成員變量是引用類型,則將引用對象的地址復(fù)制一份給克隆對象,也就是說原型對象和克隆對象的成員變量指向相同的內(nèi)存地址。

簡單來說,在淺克隆中,當(dāng)對象被復(fù)制時(shí)只復(fù)制它本身和其中包含的值類型的成員變量,而引用類型的成員對象并沒有復(fù)制。

在Java語言中,通過覆蓋Object類的clone()方法可以實(shí)現(xiàn)淺克隆。

2、深克隆

在深克隆中,無論原型對象的成員變量是值類型還是引用類型,都將復(fù)制一份給克隆對象,深克隆將原型對象的所有引用對象也復(fù)制一份給克隆對象。

簡單來說,在深克隆中,除了對象本身被復(fù)制外,對象所包含的所有成員變量也將復(fù)制。

在Java語言中,如果需要實(shí)現(xiàn)深克隆,可以通過覆蓋Object類的clone()方法實(shí)現(xiàn),也可以通過序列化(Serialization)等方式來實(shí)現(xiàn)。

(如果引用類型里面還包含很多引用類型,或者內(nèi)層引用類型的類里面又包含引用類型,使用clone方法就會(huì)很麻煩。這時(shí)我們可以用序列化的方式來實(shí)現(xiàn)對象的深克隆。)

序列化就是將對象寫到流的過程,寫到流中的對象是原有對象的一個(gè)拷貝,而原對象仍然存在于內(nèi)存中。通過序列化實(shí)現(xiàn)的拷貝不僅可以復(fù)制對象本身,而且可以復(fù)制其引用的成員對象,因此通過序列化將對象寫到一個(gè)流中,再從流里將其讀出來,可以實(shí)現(xiàn)深克隆。需要注意的是能夠?qū)崿F(xiàn)序列化的對象其類必須實(shí)現(xiàn)Serializable接口,否則無法實(shí)現(xiàn)序列化操作。

擴(kuò)展

Java語言提供的Cloneable接口和Serializable接口的代碼非常簡單,它們都是空接口,這種空接口也稱為標(biāo)識(shí)接口,標(biāo)識(shí)接口中沒有任何方法的定義,其作用是告訴JRE這些接口的實(shí)現(xiàn)類是否具有某個(gè)功能,如是否支持克隆、是否支持序列化等。

解決多層克隆問題

如果引用類型里面還包含很多引用類型,或者內(nèi)層引用類型的類里面又包含引用類型,使用clone方法就會(huì)很麻煩。這時(shí)我們可以用序列化的方式來實(shí)現(xiàn)對象的深克隆。

 
 
 
 
  1. public class Outer implements Serializable{  
  2.  private static final long serialVersionUID = 369285298572941L;  //最好是顯式聲明ID  
  3.  public Inner inner;  
  4.  //Discription:[深度復(fù)制方法,需要對象及對象所有的對象屬性都實(shí)現(xiàn)序列化]   
  5.  public Outer myclone() {  
  6.      Outer outer = null;  
  7.      try { // 將該對象序列化成流,因?yàn)閷懺诹骼锏氖菍ο蟮囊粋€(gè)拷貝,而原對象仍然存在于JVM里面。所以利用這個(gè)特性可以實(shí)現(xiàn)對象的深拷貝  
  8.          ByteArrayOutputStream baos = new ByteArrayOutputStream();  
  9.          ObjectOutputStream oos = new ObjectOutputStream(baos);  
  10.          oos.writeObject(this);  
  11.       // 將流序列化成對象  
  12.          ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());  
  13.          ObjectInputStream ois = new ObjectInputStream(bais);  
  14.          outer = (Outer) ois.readObject();  
  15.      } catch (IOException e) {  
  16.          e.printStackTrace();  
  17.      } catch (ClassNotFoundException e) {  
  18.          e.printStackTrace();  
  19.      }  
  20.      return outer;  
  21.  }  

Inner也必須實(shí)現(xiàn)Serializable,否則無法序列化:

 
 
 
 
  1. public class Inner implements Serializable{  
  2.  private static final long serialVersionUID = 872390113109L; //最好是顯式聲明ID  
  3.  public String name = "";  
  4.  public Inner(String name) {  
  5.      this.name = name; 
  6.  } 
  7.  
  8.  @Override  
  9.  public String toString() {  
  10.      return "Inner的name值為:" + name;  
  11.  }  

這樣也能使兩個(gè)對象在內(nèi)存空間內(nèi)完全獨(dú)立存在,互不影響對方的值。

總結(jié)

實(shí)現(xiàn)對象克隆有兩種方式:

1). 實(shí)現(xiàn)Cloneable接口并重寫Object類中的clone()方法;

2). 實(shí)現(xiàn)Serializable接口,通過對象的序列化和反序列化實(shí)現(xiàn)克隆,可以實(shí)現(xiàn)真正的深度克隆。

注意:基于序列化和反序列化實(shí)現(xiàn)的克隆不僅僅是深度克隆,更重要的是通過泛型限定,可以檢查出要克隆的對象是否支持序列化,這項(xiàng)檢查是編譯器完成的,不是在運(yùn)行時(shí)拋出異常,這種是方案明顯優(yōu)于使用Object類的clone方法克隆對象。讓問題在編譯的時(shí)候暴露出來總是優(yōu)于把問題留到運(yùn)行時(shí)。


分享文章:Java提高篇——對象克?。◤?fù)制)
URL鏈接:http://www.5511xx.com/article/dpjiesd.html