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

Java中的深拷貝和淺拷貝介紹

 更新時(shí)間:2014年03月17日 18:08:17   作者:  
對(duì)象拷貝(Object Copy)就是將一個(gè)對(duì)象的屬性拷貝到另一個(gè)有著相同類類型的對(duì)象中去。在程序中拷貝對(duì)象是很常見的,主要是為了在新的上下文環(huán)境中復(fù)用對(duì)象的部分或全部 數(shù)據(jù)。Java中有三種類型的對(duì)象拷貝:淺拷貝(Shallow Copy)、深拷貝(Deep Copy)、延遲拷貝(Lazy Copy)

一、引言
   對(duì)象拷貝(Object Copy)就是將一個(gè)對(duì)象的屬性拷貝到另一個(gè)有著相同類類型的對(duì)象中去。在程序中拷貝對(duì)象是很常見的,主要是為了在新的上下文環(huán)境中復(fù)用對(duì)象的部分或全部 數(shù)據(jù)。Java中有三種類型的對(duì)象拷貝:淺拷貝(Shallow Copy)、深拷貝(Deep Copy)、延遲拷貝(Lazy Copy)。

二、淺拷貝

1、什么是淺拷貝
   淺拷貝是按位拷貝對(duì)象,它會(huì)創(chuàng)建一個(gè)新對(duì)象,這個(gè)對(duì)象有著原始對(duì)象屬性值的一份精確拷貝。如果屬性是基本類型,拷貝的就是基本類型的值;如果屬性是內(nèi)存地址(引用類型),拷貝的就是內(nèi)存地址 ,因此如果其中一個(gè)對(duì)象改變了這個(gè)地址,就會(huì)影響到另一個(gè)對(duì)象。


  在圖中,SourceObject有一個(gè)int類型的屬性 "field1"和一個(gè)引用類型屬性"refObj"(引用ContainedObject類型的對(duì)象)。當(dāng)對(duì)SourceObject做淺拷貝時(shí),創(chuàng)建了CopiedObject,它有一個(gè)包含"field1"拷貝值的屬性"field2"以及仍指向refObj本身的引用。由于"field1"是基本類型,所以只是將它的值拷貝給"field2",但是由于"refObj"是一個(gè)引用類型, 所以CopiedObject指向"refObj"相同的地址。因此對(duì)SourceObject中的"refObj"所做的任何改變都會(huì)影響到CopiedObject。

2、如何實(shí)現(xiàn)淺拷貝

下面是實(shí)現(xiàn)淺拷貝的一個(gè)例子

復(fù)制代碼 代碼如下:

public class Subject {
    private String name;
   public Subject(String s) {
      name = s;
   }
   public String getName() {
      return name;
   }
   public void setName(String s) {
      name = s;
   }
}
public class Student implements Cloneable {
    // 對(duì)象引用
   private Subject subj;

   private String name;

   public Student(String s, String sub) {
      name = s;
      subj = new Subject(sub);
   }

   public Subject getSubj() {
      return subj;
   }

   public String getName() {
      return name;
   }

   public void setName(String s) {
      name = s;
   }

   /**
    *  重寫clone()方法
    * @return
    */
   public Object clone() {
      //淺拷貝
      try {
         // 直接調(diào)用父類的clone()方法
         return super.clone();
      } catch (CloneNotSupportedException e) {
         return null;
      }
   }
}
public class CopyTest {
    public static void main(String[] args) {
        // 原始對(duì)象
        Student stud = new Student("John", "Algebra");
        System.out.println("Original Object: " + stud.getName() + " - " + stud.getSubj().getName());
        // 拷貝對(duì)象
        Student clonedStud = (Student) stud.clone();
        System.out.println("Cloned Object: " + clonedStud.getName() + " - " + clonedStud.getSubj().getName());
        // 原始對(duì)象和拷貝對(duì)象是否一樣:
        System.out.println("Is Original Object the same with Cloned Object: " + (stud == clonedStud));
        // 原始對(duì)象和拷貝對(duì)象的name屬性是否一樣
        System.out.println("Is Original Object's field name the same with Cloned Object: " +
     (stud.getName() == clonedStud.getName()));
        // 原始對(duì)象和拷貝對(duì)象的subj屬性是否一樣
        System.out.println("Is Original Object's field subj the same with Cloned Object: " +
    (stud.getSubj() == clonedStud.getSubj()));
        stud.setName("Dan");
        stud.getSubj().setName("Physics");
        System.out.println("Original Object after it is updated: " + stud.getName() + " - " +
     stud.getSubj().getName());
        System.out.println("Cloned Object after updating original object: " + clonedStud.getName() +
     " - " + clonedStud.getSubj().getName());
    }
}

輸出結(jié)果如下:
 Original Object: John - Algebra
 Cloned Object: John - Algebra
 Is Original Object the same with Cloned Object: false
 Is Original Object's field name the same with Cloned Object: true
 Is Original Object's field subj the same with Cloned Object: true
 Original Object after it is updated: Dan - Physics
 Cloned Object after updating original object: John - Physics

     在這個(gè)例子中,我讓要拷貝的類Student實(shí)現(xiàn)了Clonable接口并重寫Object類的clone()方法,然后在方法內(nèi)部調(diào)用super.clone()方法。從輸出結(jié)果中我們可以看到,對(duì)原始對(duì)象stud的"name"屬性所做的改變并沒有影響到拷貝對(duì)象clonedStud,但是對(duì)引用對(duì)象subj的"name"屬性所做的改變影響到了拷貝對(duì)象clonedStud。

三、深拷貝
1、什么是深拷貝
   深拷貝會(huì)拷貝所有的屬性,并拷貝屬性指向的動(dòng)態(tài)分配的內(nèi)存。當(dāng)對(duì)象和它所引用的對(duì)象一起拷貝時(shí)即發(fā)生深拷貝。深拷貝相比于淺拷貝速度較慢并且花銷較大。



在上圖中,SourceObject有一個(gè)int類型的屬性 "field1"和一個(gè)引用類型屬性"refObj1"(引用ContainedObject類型的對(duì)象)。當(dāng)對(duì)SourceObject做深拷貝時(shí),創(chuàng)建了CopiedObject,它有一個(gè)包含"field1"拷貝值的屬性"field2"以及包含"refObj1"拷貝值的引用類型屬性"refObj2" 。因此對(duì)SourceObject中的"refObj"所做的任何改變都不會(huì)影響到CopiedObject

2、如何實(shí)現(xiàn)深拷貝
   下面是實(shí)現(xiàn)深拷貝的一個(gè)例子。只是在淺拷貝的例子上做了一點(diǎn)小改動(dòng),Subject 和CopyTest 類都沒有變化。

復(fù)制代碼 代碼如下:

public class Student implements Cloneable {
   // 對(duì)象引用
   private Subject subj;

   private String name;

   public Student(String s, String sub) {
      name = s;
      subj = new Subject(sub);
   }

   public Subject getSubj() {
      return subj;
   }

   public String getName() {
      return name;
   }

   public void setName(String s) {
      name = s;
   }

   /**
    * 重寫clone()方法
    *
    * @return
    */
   public Object clone() {
      // 深拷貝,創(chuàng)建拷貝類的一個(gè)新對(duì)象,這樣就和原始對(duì)象相互獨(dú)立
      Student s = new Student(name, subj.getName());
      return s;
   }
}

輸出結(jié)果如下:
  Original Object: John - Algebra
 Cloned Object: John - Algebra
 Is Original Object the same with Cloned Object: false
 Is Original Object's field name the same with Cloned Object: true
 Is Original Object's field subj the same with Cloned Object: false
 Original Object after it is updated: Dan - Physics
 Cloned Object after updating original object: John - Algebra

  很容易發(fā)現(xiàn)clone()方法中的一點(diǎn)變化。因?yàn)樗巧羁截?,所以你需要?jiǎng)?chuàng)建拷貝類的一個(gè)對(duì)象。因?yàn)樵赟tudent類中有對(duì)象引用,所以需要在Student類中實(shí)現(xiàn)Cloneable接口并且重寫clone方法。

3、通過(guò)序列化實(shí)現(xiàn)深拷貝
    也可以通過(guò)序列化來(lái)實(shí)現(xiàn)深拷貝。序列化是干什么的?它將整個(gè)對(duì)象圖寫入到一個(gè)持久化存儲(chǔ)文件中并且當(dāng)需要的時(shí)候把它讀取回來(lái), 這意味著當(dāng)你需要把它讀取回來(lái)時(shí)你需要整個(gè)對(duì)象圖的一個(gè)拷貝。這就是當(dāng)你深拷貝一個(gè)對(duì)象時(shí)真正需要的東西。請(qǐng)注意,當(dāng)你通過(guò)序列化進(jìn)行深拷貝時(shí),必須確保對(duì)象圖中所有類都是可序列化的。

復(fù)制代碼 代碼如下:

public class ColoredCircle implements Serializable {
   private int x;
   private int y;

   public ColoredCircle(int x, int y) {
      this.x = x;
      this.y = y;
   }

   public int getX() {
      return x;
   }

   public void setX(int x) {
      this.x = x;
   }

   public int getY() {
      return y;
   }

   public void setY(int y) {
      this.y = y;
   }

   @Override
   public String toString() {
      return "x=" + x + ", y=" + y;
   }
}
public class DeepCopy {

   public static void main(String[] args) throws IOException {
      ObjectOutputStream oos = null;
      ObjectInputStream ois = null;

      try {
         // 創(chuàng)建原始的可序列化對(duì)象
         ColoredCircle c1 = new ColoredCircle(100, 100);
         System.out.println("Original = " + c1);

         ColoredCircle c2 = null;

         // 通過(guò)序列化實(shí)現(xiàn)深拷貝
         ByteArrayOutputStream bos = new ByteArrayOutputStream();
         oos = new ObjectOutputStream(bos);
         // 序列化以及傳遞這個(gè)對(duì)象
         oos.writeObject(c1);
         oos.flush();
         ByteArrayInputStream bin = new ByteArrayInputStream(bos.toByteArray());
         ois = new ObjectInputStream(bin);
         // 返回新的對(duì)象
         c2 = (ColoredCircle) ois.readObject();

         // 校驗(yàn)內(nèi)容是否相同
         System.out.println("Copied   = " + c2);
         // 改變?cè)紝?duì)象的內(nèi)容
         c1.setX(200);
         c1.setY(200);
         // 查看每一個(gè)現(xiàn)在的內(nèi)容
         System.out.println("Original = " + c1);
         System.out.println("Copied   = " + c2);
      } catch (Exception e) {
         System.out.println("Exception in main = " + e);
      } finally {
         oos.close();
         ois.close();
      }
   }
}

輸出結(jié)果如下:
 Original = x=100, y=100
 Copied = x=100, y=100
 Original = x=200, y=200
 Copied = x=100, y=100
這里,你只需要做以下幾件事兒:
(1)確保對(duì)象圖中的所有類都是可序列化的
(2)創(chuàng)建輸入輸出流
(3)使用這個(gè)輸入輸出流來(lái)創(chuàng)建對(duì)象輸入和對(duì)象輸出流
(4)將你想要拷貝的對(duì)象傳遞給對(duì)象輸出流
(5)從對(duì)象輸入流中讀取新的對(duì)象并且轉(zhuǎn)換回你所發(fā)送的對(duì)象的類

    在這個(gè)例子中,我創(chuàng)建了一個(gè)ColoredCircle對(duì)象c1然后將它序列化 (將它寫到ByteArrayOutputStream中). 然后我反序列化這個(gè)序列化后的對(duì)象并將它保存到c2中。隨后我修改了原始對(duì)象c1。然后結(jié)果如你所見,c1不同于c2,對(duì)c1所做的任何修改都不會(huì)影響c2。

注意,序列化這種方式有其自身的限制和問(wèn)題:
因?yàn)闊o(wú)法序列化transient變量, 使用這種方法將無(wú)法拷貝transient變量。

   再就是性能問(wèn)題。創(chuàng)建一個(gè)socket, 序列化一個(gè)對(duì)象, 通過(guò)socket傳輸它, 然后反序列化它,這個(gè)過(guò)程與調(diào)用已有對(duì)象的方法相比是很慢的。所以在性能上會(huì)有天壤之別。如果性能對(duì)你的代碼來(lái)說(shuō)是至關(guān)重要的,建議不要使用這種方式。它比通過(guò)實(shí)現(xiàn)Clonable接口這種方式來(lái)進(jìn)行深拷貝幾乎多花100倍的時(shí)間。

四、延遲拷貝
   延遲拷貝是淺拷貝和深拷貝的一個(gè)組合,實(shí)際上很少會(huì)使用。 當(dāng)最開始拷貝一個(gè)對(duì)象時(shí),會(huì)使用速度較快的淺拷貝,還會(huì)使用一個(gè)計(jì)數(shù)器來(lái)記錄有多少對(duì)象共享這個(gè)數(shù)據(jù)。當(dāng)程序想要修改原始的對(duì)象時(shí),它會(huì)決定數(shù)據(jù)是否被共享(通過(guò)檢查計(jì)數(shù)器)并根據(jù)需要進(jìn)行深拷貝。
   延遲拷貝從外面看起來(lái)就是深拷貝,但是只要有可能它就會(huì)利用淺拷貝的速度。當(dāng)原始對(duì)象中的引用不經(jīng)常改變的時(shí)候可以使用延遲拷貝。由于存在計(jì)數(shù)器,效率下降很高,但只是常量級(jí)的開銷。而且, 在某些情況下, 循環(huán)引用會(huì)導(dǎo)致一些問(wèn)題。

五、如何選擇
  如果對(duì)象的屬性全是基本類型的,那么可以使用淺拷貝,但是如果對(duì)象有引用屬性,那就要基于具體的需求來(lái)選擇淺拷貝還是深拷貝。我的意思是如果對(duì)象引用任何時(shí)候都不會(huì)被改變,那么沒必要使用深拷貝,只需要使用淺拷貝就行了。如果對(duì)象引用經(jīng)常改變,那么就要使用深拷貝。沒有一成不變的規(guī)則,一切都取決于具體需求。

相關(guān)文章

  • JAVA版排序算法之快速排序示例

    JAVA版排序算法之快速排序示例

    這篇文章主要介紹了JAVA版排序算法之快速排序,結(jié)合實(shí)例形式分析了基于java版的遍歷、遞歸實(shí)現(xiàn)快速排序功能的具體步驟與操作技巧,需要的朋友可以參考下
    2017-01-01
  • MyBatis-Plus不使用數(shù)據(jù)庫(kù)默認(rèn)值的問(wèn)題及解決

    MyBatis-Plus不使用數(shù)據(jù)庫(kù)默認(rèn)值的問(wèn)題及解決

    這篇文章主要介紹了MyBatis-Plus不使用數(shù)據(jù)庫(kù)默認(rèn)值的問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-07-07
  • Java工作中常見的并發(fā)問(wèn)題處理方法總結(jié)

    Java工作中常見的并發(fā)問(wèn)題處理方法總結(jié)

    這篇文章主要介紹了Java工作中常見的并發(fā)問(wèn)題處理方法總結(jié),文章內(nèi)容講解的很清晰,有不太懂得同學(xué)可以跟著學(xué)習(xí)下
    2021-02-02
  • SpringBoot整合Shiro實(shí)現(xiàn)登錄認(rèn)證的方法

    SpringBoot整合Shiro實(shí)現(xiàn)登錄認(rèn)證的方法

    這篇文章主要介紹了SpringBoot整合Shiro實(shí)現(xiàn)登錄認(rèn)證的方法,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-02-02
  • Mybatis-Plus讀寫Mysql的Json字段的操作代碼

    Mybatis-Plus讀寫Mysql的Json字段的操作代碼

    這篇文章主要介紹了Mybatis-Plus讀寫Mysql的Json字段的操作代碼,文中通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-04-04
  • Spring Boot與ActiveMQ整合的步驟

    Spring Boot與ActiveMQ整合的步驟

    今天小編就為大家分享一篇關(guān)于Spring Boot與ActiveMQ整合的步驟,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2019-01-01
  • 騰訊開源消息中間件TubeMQ總體介紹分析

    騰訊開源消息中間件TubeMQ總體介紹分析

    這篇文章主要為大家介紹了騰訊開源消息中間件TubeMQ的總體介紹分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步
    2022-02-02
  • SpringBoot任務(wù)調(diào)度器的實(shí)現(xiàn)代碼

    SpringBoot任務(wù)調(diào)度器的實(shí)現(xiàn)代碼

    SpringBoot自帶了任務(wù)調(diào)度器,通過(guò)注解的方式使用。小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-12-12
  • java表單提交中文亂碼的解決方法

    java表單提交中文亂碼的解決方法

    這篇文章主要介紹了java表單提交中文亂碼的解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2016-10-10
  • 5個(gè)主流的Java開源IDE工具詳解

    5個(gè)主流的Java開源IDE工具詳解

    這篇文章主要介紹了5個(gè)主流的Java開源IDE工具,無(wú)論如何,Java在當(dāng)今使用的編程語(yǔ)言中始終排在前三名,在TIOBE索引中涉及700萬(wàn)到1000萬(wàn)的程序員和開發(fā)者
    2020-07-07

最新評(píng)論