欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

理解java中的深復(fù)制和淺復(fù)制

 更新時(shí)間:2016年02月14日 14:26:08   作者:naughty610  
這篇文章主要幫助大家理解java中的深復(fù)制和淺復(fù)制,對(duì)java中的深復(fù)制和淺復(fù)制進(jìn)行剖析,感興趣的小伙伴們可以參考一下

 Java語言的一個(gè)優(yōu)點(diǎn)就是取消了指針的概念,但也導(dǎo)致了許多程序員在編程中常常忽略了對(duì)象與引用的區(qū)別,本文會(huì)試圖澄清這一概念。并且由于Java不能通過簡單的賦值來解決對(duì)象復(fù)制的問題,在開發(fā)過程中,也常常要要應(yīng)用clone()方法來復(fù)制對(duì)象。本文會(huì)讓你了解什么是影子clone與深度clone,認(rèn)識(shí)它們的區(qū)別、優(yōu)點(diǎn)及缺點(diǎn)。      

看到這個(gè)標(biāo)題,是不是有點(diǎn)困惑:Java語言明確說明取消了指針,因?yàn)橹羔樛窃趲矸奖愕耐瑫r(shí)也是導(dǎo)致代碼不安全的根源,同時(shí)也會(huì)使程序的變得非常復(fù)雜難以理解,濫用指針寫成的代碼不亞于使用早已臭名昭著的"GOTO"語句。Java放棄指針的概念絕對(duì)是極其明智的。但這只是在Java語言中沒有明確的指針定義,實(shí)質(zhì)上每一個(gè)new語句返回的都是一個(gè)指針的引用,只不過在大多時(shí)候Java中不用關(guān)心如何操作這個(gè)"指針",更不用象在操作C++的指針那樣膽戰(zhàn)心驚。唯一要多多關(guān)心的是在給函數(shù)傳遞對(duì)象的時(shí)候。

package com.zoer.src; 
 
public class ObjRef { 
  Obj aObj = new Obj(); 
  int aInt = 11; 
 
  public void changeObj(Obj inObj) { 
    inObj.str = "changed value"; 
  } 
 
  public void changePri(int inInt) { 
    inInt = 22; 
  } 
 
  public static void main(String[] args) { 
    ObjRef oRef = new ObjRef(); 
 
    System.out.println("Before call changeObj() method: " + oRef.aObj); 
    oRef.changeObj(oRef.aObj); 
    System.out.println("After call changeObj() method: " + oRef.aObj); 
 
    System.out.println("==================Print Primtive================="); 
    System.out.println("Before call changePri() method: " + oRef.aInt); 
    oRef.changePri(oRef.aInt); 
    System.out.println("After call changePri() method: " + oRef.aInt); 
 
  } 
} 

package com.zoer.src; 
 
public class Obj { 
 
  String str = "init value"; 
 
  public String toString() { 
    return str; 
  } 
} 

      這段代碼的主要部分調(diào)用了兩個(gè)很相近的方法,changeObj()和changePri()。唯一不同的是它們一個(gè)把對(duì)象作為輸入?yún)?shù),另一個(gè)把Java中的基本類型int作為輸入?yún)?shù)。并且在這兩個(gè)函數(shù)體內(nèi)部都對(duì)輸入的參數(shù)進(jìn)行了改動(dòng)??此埔粯拥姆椒?,程序輸出的結(jié)果卻不太一樣。changeObj()方法真正的把輸入的參數(shù)改變了,而changePri()方法對(duì)輸入的參數(shù)沒有任何的改變。
      從這個(gè)例子知道Java對(duì)對(duì)象和基本的數(shù)據(jù)類型的處理是不一樣的。和C語言一樣,當(dāng)把Java的基本數(shù)據(jù)類型(如int,char,double等)作為入口參數(shù)傳給函數(shù)體的時(shí)候,傳入的參數(shù)在函數(shù)體內(nèi)部變成了局部變量,這個(gè)局部變量是輸入?yún)?shù)的一個(gè)拷貝,所有的函數(shù)體內(nèi)部的操作都是針對(duì)這個(gè)拷貝的操作,函數(shù)執(zhí)行結(jié)束后,這個(gè)局部變量也就完成了它的使命,它影響不到作為輸入?yún)?shù)的變量。這種方式的參數(shù)傳遞被稱為"值傳遞"。而在Java中用對(duì)象作為入口參數(shù)的傳遞則缺省為"引用傳遞",也就是說僅僅傳遞了對(duì)象的一個(gè)"引用",這個(gè)"引用"的概念同C語言中的指針引用是一樣的。當(dāng)函數(shù)體內(nèi)部對(duì)輸入變量改變時(shí),實(shí)質(zhì)上就是在對(duì)這個(gè)對(duì)象的直接操作。
      除了在函數(shù)傳值的時(shí)候是"引用傳遞",在任何用"="向?qū)ο笞兞抠x值的時(shí)候都是"引用傳遞"。就是類似于給變量再起一個(gè)別名。兩個(gè)名字都指向內(nèi)存中的同一個(gè)對(duì)象。
      在實(shí)際編程過程中,我們常常要遇到這種情況:有一個(gè)對(duì)象A,在某一時(shí)刻A中已經(jīng)包含了一些有效值,此時(shí)可能會(huì)需要一個(gè)和A完全相同新對(duì)象B,并且此后對(duì)B任何改動(dòng)都不會(huì)影響到A中的值,也就是說,A與B是兩個(gè)獨(dú)立的對(duì)象,但B的初始值是由A對(duì)象確定的。在Java語言中,用簡單的賦值語句是不能滿足這種需求的。要滿足這種需求雖然有很多途徑,但實(shí)現(xiàn)clone()方法是其中最簡單,也是最高效的手段。
      Java的所有類都默認(rèn)繼承java.lang.Object類,在java.lang.Object類中有一個(gè)方法clone()。JDK API的說明文檔解釋這個(gè)方法將返回Object對(duì)象的一個(gè)拷貝。要說明的有兩點(diǎn):一是拷貝對(duì)象返回的是一個(gè)新對(duì)象,而不是一個(gè)引用。二是拷貝對(duì)象與用new操作符返回的新對(duì)象的區(qū)別就是這個(gè)拷貝已經(jīng)包含了一些原來對(duì)象的信息,而不是對(duì)象的初始信息。
      怎樣應(yīng)用clone()方法?

一個(gè)很典型的調(diào)用clone()代碼如下:

public class CloneClass implements Cloneable { 
  public int aInt; 
 
  public Object clone() { 
    CloneClass o = null; 
    try { 
      o = (CloneClass) super.clone(); 
    } catch (CloneNotSupportedException e) { 
      e.printStackTrace(); 
    } 
    return o; 
  } 
} 

      有三個(gè)值得注意的地方,一是希望能實(shí)現(xiàn)clone功能的CloneClass類實(shí)現(xiàn)了Cloneable接口,這個(gè)接口屬于java.lang包,java.lang包已經(jīng)被缺省的導(dǎo)入類中,所以不需要寫成java.lang.Cloneable。另一個(gè)值得請注意的是重載了clone()方法。最后在clone()方法中調(diào)用了super.clone(),這也意味著無論clone類的繼承結(jié)構(gòu)是什么樣的,super.clone()直接或間接調(diào)用了java.lang.Object類的clone()方法。下面再詳細(xì)的解釋一下這幾點(diǎn)。
      應(yīng)該說第三點(diǎn)是最重要的,仔細(xì)觀察一下Object類的clone()一個(gè)native方法,native方法的效率一般來說都是遠(yuǎn)高于java中的非native方法。這也解釋了為什么要用Object中clone()方法而不是先new一個(gè)類,然后把原始對(duì)象中的信息賦到新對(duì)象中,雖然這也實(shí)現(xiàn)了clone功能。對(duì)于第二點(diǎn),也要觀察Object類中的clone()還是一個(gè)protected屬性的方法。這也意味著如果要應(yīng)用clone()方法,必須繼承Object類,在Java中所有的類是缺省繼承Object類的,也就不用關(guān)心這點(diǎn)了。然后重載clone()方法。還有一點(diǎn)要考慮的是為了讓其它類能調(diào)用這個(gè)clone類的clone()方法,重載之后要把clone()方法的屬性設(shè)置為public。
       那么clone類為什么還要實(shí)現(xiàn)Cloneable接口呢?稍微注意一下,Cloneable接口是不包含任何方法的!其實(shí)這個(gè)接口僅僅是一個(gè)標(biāo)志,而且這個(gè)標(biāo)志也僅僅是針對(duì)Object類中clone()方法的,如果clone類沒有實(shí)現(xiàn)Cloneable接口,并調(diào)用了Object的clone()方法(也就是調(diào)用了super.Clone()方法),那么Object的clone()方法就會(huì)拋出CloneNotSupportedException異常。
      以上是clone的最基本的步驟,想要完成一個(gè)成功的clone,還要了解什么是"影子clone"和"深度clone"。 
      什么是影子clone?

package com.zoer.src; 
 
class UnCloneA { 
  private int i; 
 
  public UnCloneA(int ii) { 
    i = ii; 
  } 
 
  public void doublevalue() { 
    i *= 2; 
  } 
 
  public String toString() { 
    return Integer.toString(i); 
  } 
} 
 
class CloneB implements Cloneable { 
  public int aInt; 
  public UnCloneA unCA = new UnCloneA(111); 
 
  public Object clone() { 
    CloneB o = null; 
    try { 
      o = (CloneB) super.clone(); 
    } catch (CloneNotSupportedException e) { 
      e.printStackTrace(); 
    } 
    return o; 
  } 
} 
 
public class ObjRef { 
  public static void main(String[] a) { 
    CloneB b1 = new CloneB(); 
    b1.aInt = 11; 
    System.out.println("before clone,b1.aInt = " + b1.aInt); 
    System.out.println("before clone,b1.unCA = " + b1.unCA); 
 
    CloneB b2 = (CloneB) b1.clone(); 
    b2.aInt = 22; 
    b2.unCA.doublevalue(); 
    System.out.println("================================="); 
    System.out.println("after clone,b1.aInt = " + b1.aInt); 
    System.out.println("after clone,b1.unCA = " + b1.unCA); 
    System.out.println("================================="); 
    System.out.println("after clone,b2.aInt = " + b2.aInt); 
    System.out.println("after clone,b2.unCA = " + b2.unCA); 
  } 
} 

輸出結(jié)果:

before clone,b1.aInt = 11
before clone,b1.unCA = 111
=================================
after clone,b1.aInt = 11
after clone,b1.unCA = 222
=================================
after clone,b2.aInt = 22
after clone,b2.unCA = 222

       輸出的結(jié)果說明int類型的變量aInt和UnCloneA的實(shí)例對(duì)象unCA的clone結(jié)果不一致,int類型是真正的被clone了,因?yàn)楦淖兞薭2中的aInt變量,對(duì)b1的aInt沒有產(chǎn)生影響,也就是說,b2.aInt與b1.aInt已經(jīng)占據(jù)了不同的內(nèi)存空間,b2.aInt是b1.aInt的一個(gè)真正拷貝。相反,對(duì)b2.unCA的改變同時(shí)改變了b1.unCA,很明顯,b2.unCA和b1.unCA是僅僅指向同一個(gè)對(duì)象的不同引用!從中可以看出,調(diào)用Object類中clone()方法產(chǎn)生的效果是:先在內(nèi)存中開辟一塊和原始對(duì)象一樣的空間,然后原樣拷貝原始對(duì)象中的內(nèi)容。對(duì)基本數(shù)據(jù)類型,這樣的操作是沒有問題的,但對(duì)非基本類型變量,我們知道它們保存的僅僅是對(duì)象的引用,這也導(dǎo)致clone后的非基本類型變量和原始對(duì)象中相應(yīng)的變量指向的是同一個(gè)對(duì)象。
       大多時(shí)候,這種clone的結(jié)果往往不是我們所希望的結(jié)果,這種clone也被稱為"影子clone"。要想讓b2.unCA指向與b2.unCA不同的對(duì)象,而且b2.unCA中還要包含b1.unCA中的信息作為初始信息,就要實(shí)現(xiàn)深度clone。
       怎么進(jìn)行深度clone?
       把上面的例子改成深度clone很簡單,需要兩個(gè)改變:一是讓UnCloneA類也實(shí)現(xiàn)和CloneB類一樣的clone功能(實(shí)現(xiàn)Cloneable接口,重載clone()方法)。二是在CloneB的clone()方法中加入一句o.unCA = (UnCloneA)unCA.clone();

package com.zoer.src; 
 
class UnCloneA implements Cloneable { 
  private int i; 
 
  public UnCloneA(int ii) { 
    i = ii; 
  } 
 
  public void doublevalue() { 
    i *= 2; 
  } 
 
  public String toString() { 
    return Integer.toString(i); 
  } 
 
  public Object clone() { 
    UnCloneA o = null; 
    try { 
      o = (UnCloneA) super.clone(); 
    } catch (CloneNotSupportedException e) { 
      e.printStackTrace(); 
    } 
    return o; 
  } 
} 
 
class CloneB implements Cloneable { 
  public int aInt; 
  public UnCloneA unCA = new UnCloneA(111); 
 
  public Object clone() { 
    CloneB o = null; 
    try { 
      o = (CloneB) super.clone(); 
    } catch (CloneNotSupportedException e) { 
      e.printStackTrace(); 
    } 
    o.unCA = (UnCloneA) unCA.clone(); 
    return o; 
  } 
} 
 
public class CloneMain { 
  public static void main(String[] a) { 
    CloneB b1 = new CloneB(); 
    b1.aInt = 11; 
    System.out.println("before clone,b1.aInt = " + b1.aInt); 
    System.out.println("before clone,b1.unCA = " + b1.unCA); 
 
    CloneB b2 = (CloneB) b1.clone(); 
    b2.aInt = 22; 
    b2.unCA.doublevalue(); 
    System.out.println("================================="); 
    System.out.println("after clone,b1.aInt = " + b1.aInt); 
    System.out.println("after clone,b1.unCA = " + b1.unCA); 
    System.out.println("================================="); 
    System.out.println("after clone,b2.aInt = " + b2.aInt); 
    System.out.println("after clone,b2.unCA = " + b2.unCA); 
  } 
} 

輸出結(jié)果:

before clone,b1.aInt = 11
before clone,b1.unCA = 111
=================================
after clone,b1.aInt = 11
after clone,b1.unCA = 111
=================================
after clone,b2.aInt = 22
after clone,b2.unCA = 222

      可以看出,現(xiàn)在b2.unCA的改變對(duì)b1.unCA沒有產(chǎn)生影響。此時(shí)b1.unCA與b2.unCA指向了兩個(gè)不同的UnCloneA實(shí)例,而且在CloneB b2 = (CloneB)b1.clone();調(diào)用的那一刻b1和b2擁有相同的值,在這里,b1.i = b2.i = 11。
        要知道不是所有的類都能實(shí)現(xiàn)深度clone的。例如,如果把上面的CloneB類中的UnCloneA類型變量改成StringBuffer類型,看一下JDK API中關(guān)于StringBuffer的說明,StringBuffer沒有重載clone()方法,更為嚴(yán)重的是StringBuffer還是一個(gè)final類,這也是說我們也不能用繼承的辦法間接實(shí)現(xiàn)StringBuffer的clone。如果一個(gè)類中包含有StringBuffer類型對(duì)象或和StringBuffer相似類的對(duì)象,我們有兩種選擇:要么只能實(shí)現(xiàn)影子clone,要么就在類的clone()方法中加一句(假設(shè)是SringBuffer對(duì)象,而且變量名仍是unCA): o.unCA = new StringBuffer(unCA.toString()); //原來的是:o.unCA = (UnCloneA)unCA.clone();
       還要知道的是除了基本數(shù)據(jù)類型能自動(dòng)實(shí)現(xiàn)深度clone以外,String對(duì)象是一個(gè)例外,它c(diǎn)lone后的表現(xiàn)好象也實(shí)現(xiàn)了深度clone,雖然這只是一個(gè)假象,但卻大大方便了我們的編程。
       Clone中String和StringBuffer的區(qū)別
       應(yīng)該說明的是,這里不是著重說明String和StringBuffer的區(qū)別,但從這個(gè)例子里也能看出String類的一些與眾不同的地方。
       下面的例子中包括兩個(gè)類,CloneC類包含一個(gè)String類型變量和一個(gè)StringBuffer類型變量,并且實(shí)現(xiàn)了clone()方法。在StrClone類中聲明了CloneC類型變量c1,然后調(diào)用c1的clone()方法生成c1的拷貝c2,在對(duì)c2中的String和StringBuffer類型變量用相應(yīng)的方法改動(dòng)之后打印結(jié)果:

package com.zoer.src; 
 
class CloneC implements Cloneable { 
  public String str; 
  public StringBuffer strBuff; 
 
  public Object clone() { 
    CloneC o = null; 
    try { 
      o = (CloneC) super.clone(); 
    } catch (CloneNotSupportedException e) { 
      e.printStackTrace(); 
    } 
    return o; 
  } 
 
} 
 
public class StrClone { 
  public static void main(String[] a) { 
    CloneC c1 = new CloneC(); 
    c1.str = new String("initializeStr"); 
    c1.strBuff = new StringBuffer("initializeStrBuff"); 
    System.out.println("before clone,c1.str = " + c1.str); 
    System.out.println("before clone,c1.strBuff = " + c1.strBuff); 
 
    CloneC c2 = (CloneC) c1.clone(); 
    c2.str = c2.str.substring(0, 5); 
    c2.strBuff = c2.strBuff.append(" change strBuff clone"); 
    System.out.println("================================="); 
    System.out.println("after clone,c1.str = " + c1.str); 
    System.out.println("after clone,c1.strBuff = " + c1.strBuff); 
    System.out.println("================================="); 
    System.out.println("after clone,c2.str = " + c2.str); 
    System.out.println("after clone,c2.strBuff = " + c2.strBuff); 
  } 
} 

執(zhí)行結(jié)果:

<span style="font-family:'Microsoft YaHei';"><span style="font-size:16px;">before clone,c1.str = initializeStr 
before clone,c1.strBuff = initializeStrBuff 
================================= 
after clone,c1.str = initializeStr 
after clone,c1.strBuff = initializeStrBuff change strBuff clone 
================================= 
after clone,c2.str = initi 
after clone,c2.strBuff = initializeStrBuff change strBuff clone 
</span></span> 

        打印的結(jié)果可以看出,String類型的變量好象已經(jīng)實(shí)現(xiàn)了深度clone,因?yàn)閷?duì)c2.str的改動(dòng)并沒有影響到c1.str!難道Java把Sring類看成了基本數(shù)據(jù)類型?其實(shí)不然,這里有一個(gè)小小的把戲,秘密就在于c2.str = c2.str.substring(0,5)這一語句!實(shí)質(zhì)上,在clone的時(shí)候c1.str與c2.str仍然是引用,而且都指向了同一個(gè)String對(duì)象。但在執(zhí)行c2.str = c2.str.substring(0,5)的時(shí)候,它作用相當(dāng)于生成了一個(gè)新的String類型,然后又賦回給c2.str。這是因?yàn)镾tring被Sun公司的工程師寫成了一個(gè)不可更改的類(immutable class),在所有String類中的函數(shù)都不能更改自身的值。

以上就是本文的全部內(nèi)容,希望對(duì)大家理解java中的深復(fù)制和淺復(fù)制有所幫助。

相關(guān)文章

  • Netty中最簡單的粘包解析方法分享

    Netty中最簡單的粘包解析方法分享

    黏包 是指網(wǎng)絡(luò)上有多條數(shù)據(jù)發(fā)送給服務(wù)端, 但是由于某種原因這些數(shù)據(jù)在被接受的時(shí)候進(jìn)行了重新組合,本文分享了一種最簡單的黏包解析方法, 非常適用于初初初級(jí)選手
    2023-05-05
  • Eclipse中Properties和yml配置文件注釋亂碼的解決

    Eclipse中Properties和yml配置文件注釋亂碼的解決

    這篇文章主要介紹了Eclipse中Properties和yml配置文件注釋亂碼的解決,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-10-10
  • 微服務(wù)eureka和nacos案例詳解

    微服務(wù)eureka和nacos案例詳解

    這篇文章主要介紹了微服務(wù)eureka和nacos,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-06-06
  • feign實(shí)現(xiàn)傳遞參數(shù)的三種方式小結(jié)

    feign實(shí)現(xiàn)傳遞參數(shù)的三種方式小結(jié)

    這篇文章主要介紹了feign實(shí)現(xiàn)傳遞參數(shù)的三種方式小結(jié),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • java虛擬機(jī)學(xué)習(xí)筆記進(jìn)階篇

    java虛擬機(jī)學(xué)習(xí)筆記進(jìn)階篇

    在本篇內(nèi)容里小編給大家分享了關(guān)于java虛擬機(jī)學(xué)習(xí)筆記的進(jìn)階內(nèi)容,需要的朋友們跟著學(xué)習(xí)下。
    2019-06-06
  • Spring中Bean的三種實(shí)例化方式詳解

    Spring中Bean的三種實(shí)例化方式詳解

    這篇文章主要給大家介紹了關(guān)于Spring中實(shí)例化bean的三種方式:構(gòu)造方法、靜態(tài)工廠和實(shí)例工廠,對(duì)我們學(xué)習(xí)有一定的參考價(jià)值,需要的小伙伴可以了解一下
    2022-06-06
  • Springmvc如何返回xml及json格式數(shù)據(jù)

    Springmvc如何返回xml及json格式數(shù)據(jù)

    這篇文章主要介紹了Springmvc如何返回xml及json格式數(shù)據(jù),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-09-09
  • SSM?Mapper文件查詢出返回?cái)?shù)據(jù)查不到個(gè)別字段的問題

    SSM?Mapper文件查詢出返回?cái)?shù)據(jù)查不到個(gè)別字段的問題

    這篇文章主要介紹了SSM?Mapper文件查詢出返回?cái)?shù)據(jù)查不到個(gè)別字段的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • 詳解Elasticsearch如何實(shí)現(xiàn)簡單的腳本排序

    詳解Elasticsearch如何實(shí)現(xiàn)簡單的腳本排序

    Elasticsearch?是位于?Elastic?Stack?核心的分布式搜索和分析引擎,可以為所有類型的數(shù)據(jù)提供近乎實(shí)時(shí)的搜索和分析。本文主要介紹了Elasticsearch如何實(shí)現(xiàn)簡單的腳本排序,感興趣的可以了解一下
    2023-01-01
  • 關(guān)于使用POI向word中添加圖片的問題

    關(guān)于使用POI向word中添加圖片的問題

    這篇文章主要介紹了關(guān)于使用POI向word中添加圖片的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-12-12

最新評(píng)論