Java深拷貝和淺拷貝

目錄介紹

  • 01.對象拷貝有哪些
  • 02.理解淺拷貝php

    • 2.1 什麼是淺拷貝
    • 2.2 實現淺拷貝案例
  • 03.理解深拷貝git

    • 3.1 什麼是深拷貝
    • 3.2 實現深拷貝案例
  • 04.序列化進行拷貝github

    • 4.1 序列化屬於深拷貝
    • 4.2 注意要點
    • 4.3 序列化案例
  • 05.延遲拷貝
  • 06.如何選擇拷貝方式
  • 07.數組的拷貝面試

    • 7.1 基本數據類型數組
    • 7.2 引用數據類型數組
  • 08.集合的拷貝segmentfault

    • 8.1 集合淺拷貝
    • 8.2 集合深拷貝

好消息

  • 博客筆記大彙總【16年3月到至今】,包括Java基礎及深刻知識點,Android技術博客,Python學習筆記等等,還包括平時開發中遇到的bug彙總,固然也在工做之餘收集了大量的面試題,長期更新維護而且修正,持續完善……開源的文件是markdown格式的!同時也開源了生活博客,從12年起,積累共計N篇[近100萬字,陸續搬到網上],轉載請註明出處,謝謝!
  • 連接地址:https://github.com/yangchong2...
  • 若是以爲好,能夠star一下,謝謝!固然也歡迎提出建議,萬事起於忽微,量變引發質變!

01.對象拷貝有哪些

  • 對象拷貝(Object Copy)就是將一個對象的屬性拷貝到另外一個有着相同類類型的對象中去。在程序中拷貝對象是很常見的,主要是爲了在新的上下文環境中複用對象的部分或所有數據。
  • Java中有三種類型的對象拷貝:淺拷貝(Shallow Copy)、深拷貝(Deep Copy)、延遲拷貝(Lazy Copy)。

02.理解淺拷貝

2.1 什麼是淺拷貝

  • 淺拷貝是按位拷貝對象,它會建立一個新對象,這個對象有着原始對象屬性值的一份精確拷貝。數組

    • 若是屬性是基本類型,拷貝的就是基本類型的值;若是屬性是內存地址(引用類型),拷貝的就是內存地址 ,所以若是其中一個對象改變了這個地址,就會影響到另外一個對象。
    • image
    • 在上圖中,SourceObject有一個int類型的屬性 "field1"和一個引用類型屬性"refObj"(引用ContainedObject類型的對象)。當對SourceObject作淺拷貝時,建立了CopiedObject,它有一個包含"field1"拷貝值的屬性"field2"以及仍指向refObj自己的引用。因爲"field1"是基本類型,因此只是將它的值拷貝給"field2",可是因爲"refObj"是一個引用類型, 因此CopiedObject指向"refObj"相同的地址。所以對SourceObject中的"refObj"所作的任何改變都會影響到CopiedObject。

2.2 如何實現淺拷貝

  • 下面來看一看實現淺拷貝的一個例子markdown

    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 { 
     
       // 對象引用 
       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()方法
*/ 
   public Object clone() { 
      //淺拷貝 
      try { 
         // 直接調用父類的clone()方法
         return super.clone(); 
      } catch (CloneNotSupportedException e) { 
         return null; 
      } 
   } 
}
```

```
private void test1(){
    // 原始對象
    Student stud = new Student("楊充", "瀟湘劍雨");
    System.out.println("原始對象: " + stud.getName() + " - " + stud.getSubj().getName());

    // 拷貝對象
    Student clonedStud = (Student) stud.clone();
    System.out.println("拷貝對象: " + clonedStud.getName() + " - " + clonedStud.getSubj().getName());

    // 原始對象和拷貝對象是否同樣:
    System.out.println("原始對象和拷貝對象是否同樣: " + (stud == clonedStud));
    // 原始對象和拷貝對象的name屬性是否同樣
    System.out.println("原始對象和拷貝對象的name屬性是否同樣: " + (stud.getName() == clonedStud.getName()));
    // 原始對象和拷貝對象的subj屬性是否同樣
    System.out.println("原始對象和拷貝對象的subj屬性是否同樣: " + (stud.getSubj() == clonedStud.getSubj()));

    stud.setName("小楊逗比");
    stud.getSubj().setName("瀟湘劍雨大俠");
    System.out.println("更新後的原始對象: " + stud.getName() + " - " + stud.getSubj().getName());
    System.out.println("更新原始對象後的克隆對象: " + clonedStud.getName() + " - " + clonedStud.getSubj().getName());
}
```
  • 輸出結果以下:socket

    2019-03-23 13:50:57.518 24704-24704/com.ycbjie.other I/System.out: 原始對象: 楊充 - 瀟湘劍雨
    2019-03-23 13:50:57.519 24704-24704/com.ycbjie.other I/System.out: 拷貝對象: 楊充 - 瀟湘劍雨
    2019-03-23 13:50:57.519 24704-24704/com.ycbjie.other I/System.out: 原始對象和拷貝對象是否同樣: false
    2019-03-23 13:50:57.519 24704-24704/com.ycbjie.other I/System.out: 原始對象和拷貝對象的name屬性是否同樣: true
    2019-03-23 13:50:57.519 24704-24704/com.ycbjie.other I/System.out: 原始對象和拷貝對象的subj屬性是否同樣: true
    2019-03-23 13:50:57.519 24704-24704/com.ycbjie.other I/System.out: 更新後的原始對象: 小楊逗比 - 瀟湘劍雨大俠
    2019-03-23 13:50:57.519 24704-24704/com.ycbjie.other I/System.out: 更新原始對象後的克隆對象: 楊充 - 瀟湘劍雨大俠
  • 能夠得出的結論ide

    • 在這個例子中,讓要拷貝的類Student實現了Clonable接口並重寫Object類的clone()方法,而後在方法內部調用super.clone()方法。從輸出結果中咱們能夠看到,對原始對象stud的"name"屬性所作的改變並無影響到拷貝對象clonedStud,可是對引用對象subj的"name"屬性所作的改變影響到了拷貝對象clonedStud。

03.理解深拷貝

3.1 什麼是深拷貝

  • 深拷貝會拷貝全部的屬性,並拷貝屬性指向的動態分配的內存。當對象和它所引用的對象一塊兒拷貝時即發生深拷貝。深拷貝相比於淺拷貝速度較慢而且花銷較大。函數

    • image
    • 在上圖中,SourceObject有一個int類型的屬性 "field1"和一個引用類型屬性"refObj1"(引用ContainedObject類型的對象)。當對SourceObject作深拷貝時,建立了CopiedObject,它有一個包含"field1"拷貝值的屬性"field2"以及包含"refObj1"拷貝值的引用類型屬性"refObj2" 。所以對SourceObject中的"refObj"所作的任何改變都不會影響到CopiedObject

3.2 實現深拷貝案例

  • 下面是實現深拷貝的一個例子。只是在淺拷貝的例子上作了一點小改動,Subject 和CopyTest 類都沒有變化。

    public class Student implements Cloneable { 
       // 對象引用 
       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()方法 
        *
*/ 
   public Object clone() { 
      // 深拷貝,建立拷貝類的一個新對象,這樣就和原始對象相互獨立
      Student s = new Student(name, subj.getName()); 
      return s; 
   } 
}
```
  • 輸出結果以下:

    2019-03-23 13:53:48.096 25123-25123/com.ycbjie.other I/System.out: 原始對象: 楊充 - 瀟湘劍雨
    2019-03-23 13:53:48.096 25123-25123/com.ycbjie.other I/System.out: 拷貝對象: 楊充 - 瀟湘劍雨
    2019-03-23 13:53:48.096 25123-25123/com.ycbjie.other I/System.out: 原始對象和拷貝對象是否同樣: false
    2019-03-23 13:53:48.096 25123-25123/com.ycbjie.other I/System.out: 原始對象和拷貝對象的name屬性是否同樣: true
    2019-03-23 13:53:48.096 25123-25123/com.ycbjie.other I/System.out: 原始對象和拷貝對象的subj屬性是否同樣: false
    2019-03-23 13:53:48.096 25123-25123/com.ycbjie.other I/System.out: 更新後的原始對象: 小楊逗比 - 瀟湘劍雨大俠
    2019-03-23 13:53:48.096 25123-25123/com.ycbjie.other I/System.out: 更新原始對象後的克隆對象: 楊充 - 瀟湘劍雨
  • 得出的結論

    • 很容易發現clone()方法中的一點變化。由於它是深拷貝,因此你須要建立拷貝類的一個對象。由於在Student類中有對象引用,因此須要在Student類中實現Cloneable接口而且重寫clone方法。

04.序列化進行拷貝

4.1 序列化屬於深拷貝

  • 可能你會問,序列化是屬於那種類型拷貝?答案是:經過序列化來實現深拷貝。能夠思考一下,爲什麼序列化對象要用深拷貝而不是用淺拷貝呢?

4.2 注意要點

  • 能夠序列化是幹什麼的?它將整個對象圖寫入到一個持久化存儲文件中而且當須要的時候把它讀取回來, 這意味着當你須要把它讀取回來時你須要整個對象圖的一個拷貝。這就是當你深拷貝一個對象時真正須要的東西。請注意,當你經過序列化進行深拷貝時,必須確保對象圖中全部類都是可序列化的。

4.3 序列化案例

  • 看一下下面案例,很簡單,只須要實現Serializable這個接口。Android中還能夠實現Parcelable接口。

    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; 
       } 
    }
    private void test3() {
        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;
        try {
            // 建立原始的可序列化對象
            DouBi c1 = new DouBi(100, 100);
            System.out.println("原始的對象 = " + c1);
            DouBi c2 = null;
            // 經過序列化實現深拷貝
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(bos);
            // 序列化以及傳遞這個對象
            oos.writeObject(c1);
            oos.flush();
            ByteArrayInputStream bin = new ByteArrayInputStream(bos.toByteArray());
            ois = new ObjectInputStream(bin);
            // 返回新的對象
            c2 = (DouBi) ois.readObject();
            // 校驗內容是否相同
            System.out.println("複製後的對象   = " + c2);
            // 改變原始對象的內容
            c1.setX(200);
            c1.setY(200);
            // 查看每個如今的內容
            System.out.println("查看原始的對象 = " + c1);
            System.out.println("查看複製的對象 = " + c2);
        } catch (IOException e) {
            System.out.println("Exception in main = " + e);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            if (oos != null) {
                try {
                    oos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (ois != null) {
                try {
                    ois.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
  • 輸出結果以下:

    2019-03-23 13:53:48.096 25123-25123/com.ycbjie.other I/System.out: 原始的對象 = x=100, y=100
    2019-03-23 13:53:48.096 25123-25123/com.ycbjie.other I/System.out: 複製後的對象   = x=100, y=100
    2019-03-23 13:53:48.096 25123-25123/com.ycbjie.other I/System.out: 查看原始的對象 = x=200, y=200
    2019-03-23 13:53:48.096 25123-25123/com.ycbjie.other I/System.out: 查看複製的對象   = x=100, y=100
  • 注意:須要作如下幾件事兒:

    • 確保對象圖中的全部類都是可序列化的
    • 建立輸入輸出流
    • 使用這個輸入輸出流來建立對象輸入和對象輸出流
    • 將你想要拷貝的對象傳遞給對象輸出流
    • 從對象輸入流中讀取新的對象而且轉換回你所發送的對象的類
  • 得出的結論

    • 在這個例子中,建立了一個DouBi對象c1而後將它序列化 (將它寫到ByteArrayOutputStream中). 而後我反序列化這個序列化後的對象並將它保存到c2中。隨後我修改了原始對象c1。而後結果如你所見,c1不一樣於c2,對c1所作的任何修改都不會影響c2。
    • 注意,序列化這種方式有其自身的限制和問題:由於沒法序列化transient變量, 使用這種方法將沒法拷貝transient變量。再就是性能問題。建立一個socket, 序列化一個對象, 經過socket傳輸它, 而後反序列化它,這個過程與調用已有對象的方法相比是很慢的。因此在性能上會有天壤之別。若是性能對你的代碼來講是相當重要的,建議不要使用這種方式。它比經過實現Clonable接口這種方式來進行深拷貝幾乎多花100倍的時間。

05.延遲拷貝

  • 延遲拷貝是淺拷貝和深拷貝的一個組合,實際上不多會使用。這個之前幾乎都沒據說過,後來看書才知道有這麼一種拷貝!
  • 當最開始拷貝一個對象時,會使用速度較快的淺拷貝,還會使用一個計數器來記錄有多少對象共享這個數據。當程序想要修改原始的對象時,它會決定數據是否被共享(經過檢查計數器)並根據須要進行深拷貝。
  • 延遲拷貝從外面看起來就是深拷貝,可是隻要有可能它就會利用淺拷貝的速度。當原始對象中的引用不常常改變的時候可使用延遲拷貝。因爲存在計數器,效率降低很高,但只是常量級的開銷。並且, 在某些狀況下, 循環引用會致使一些問題。

06.如何選擇拷貝方式

  • 若是對象的屬性全是基本類型的,那麼可使用淺拷貝。
  • 若是對象有引用屬性,那就要基於具體的需求來選擇淺拷貝仍是深拷貝。
  • 意思是若是對象引用任什麼時候候都不會被改變,那麼不必使用深拷貝,只須要使用淺拷貝就好了。若是對象引用常常改變,那麼就要使用深拷貝。沒有一成不變的規則,一切都取決於具體需求。

07.數組的拷貝

  • 數組除了默認實現了clone()方法以外,還提供了Arrays.copyOf方法用於拷貝,這二者都是淺拷貝。

7.1 基本數據類型數組

  • 以下所示

    public void test4() {
        int[] lNumbers1 = new int[5];
        int[] rNumbers1 = Arrays.copyOf(lNumbers1, lNumbers1.length);
        rNumbers1[0] = 1;
        boolean first = lNumbers1[0] == rNumbers1[0];
        Log.d("小楊逗比", "lNumbers2[0]=" + lNumbers1[0] + ",rNumbers2[0]=" + rNumbers1[0]+"---"+first);
    
        int[] lNumbers3 = new int[5];
        int[] rNumbers3 = lNumbers3.clone();
        rNumbers3[0] = 1;
        boolean second = lNumbers3[0] == rNumbers3[0];
        Log.d("小楊逗比", "lNumbers3[0]=" + lNumbers3[0] + ",rNumbers3[0]=" + rNumbers3[0]+"---"+second);
    }
  • 打印結果以下所示

    2019-03-25 14:28:09.907 30316-30316/org.yczbj.ycrefreshview D/小楊逗比: lNumbers2[0]=0,rNumbers2[0]=1---false
    2019-03-25 14:28:09.907 30316-30316/org.yczbj.ycrefreshview D/小楊逗比: lNumbers3[0]=0,rNumbers3[0]=1---false

7.2 引用數據類型數組

  • 以下所示

    public static void test5() {
        People[] lNumbers1 = new People[5];
        lNumbers1[0] = new People();
        People[] rNumbers1 = lNumbers1;
        boolean first = lNumbers1[0].equals(rNumbers1[0]);
        Log.d("小楊逗比", "lNumbers1[0]=" + lNumbers1[0] + ",rNumbers1[0]=" + rNumbers1[0]+"--"+first);
    
        People[] lNumbers2 = new People[5];
        lNumbers2[0] = new People();
        People[] rNumbers2 = Arrays.copyOf(lNumbers2, lNumbers2.length);
        boolean second = lNumbers2[0].equals(rNumbers2[0]);
        Log.d("小楊逗比", "lNumbers2[0]=" + lNumbers2[0] + ",rNumbers2[0]=" + rNumbers2[0]+"--"+second);
    
        People[] lNumbers3 = new People[5];
        lNumbers3[0] = new People();
        People[] rNumbers3 = lNumbers3.clone();
        boolean third = lNumbers3[0].equals(rNumbers3[0]);
        Log.d("小楊逗比", "lNumbers3[0]=" + lNumbers3[0] + ",rNumbers3[0]=" + rNumbers3[0]+"--"+third);
    }
    
    public static class People implements Cloneable {
    
        int age;
        Holder holder;
    
        @Override
        protected Object clone() {
            try {
                return super.clone();
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        public static class Holder {
            int holderValue;
        }
    }
  • 打印日誌以下

    2019-03-25 14:53:17.054 31093-31093/org.yczbj.ycrefreshview D/小楊逗比: lNumbers1[0]=org.yczbj.ycrefreshview.MainActivity$People@46a2c18,rNumbers1[0]=org.yczbj.ycrefreshview.MainActivity$People@46a2c18--true
    2019-03-25 14:53:17.054 31093-31093/org.yczbj.ycrefreshview D/小楊逗比: lNumbers2[0]=org.yczbj.ycrefreshview.MainActivity$People@d344671,rNumbers2[0]=org.yczbj.ycrefreshview.MainActivity$People@d344671--true
    2019-03-25 14:53:17.054 31093-31093/org.yczbj.ycrefreshview D/小楊逗比: lNumbers3[0]=org.yczbj.ycrefreshview.MainActivity$People@91e9c56,rNumbers3[0]=org.yczbj.ycrefreshview.MainActivity$People@91e9c56--true

08.集合的拷貝

  • 集合的拷貝也是咱們平時常常會遇到的,通常狀況下,咱們都是用淺拷貝來實現,即經過構造函數或者clone方法。

8.1 集合淺拷貝

  • 構造函數和 clone() 默認都是淺拷貝

    public static void test6() {
        ArrayList<People> lPeoples = new ArrayList<>();
        People people1 = new People();
        lPeoples.add(people1);
        Log.d("小楊逗比", "lPeoples[0]=" + lPeoples.get(0));
        ArrayList<People> rPeoples = (ArrayList<People>) lPeoples.clone();
        Log.d("小楊逗比", "rPeoples[0]=" + rPeoples.get(0));
        boolean b = lPeoples.get(0).equals(rPeoples.get(0));
        Log.d("小楊逗比", "比較兩個對象" + b);
    }
    
    public static class People implements Cloneable {
    
        int age;
        Holder holder;
    
        @Override
        protected Object clone() {
            try {
                People people = (People) super.clone();
                people.holder = (People.Holder) this.holder.clone();
                return people;
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        public static class Holder implements Cloneable {
    
            int holderValue;
    
            @Override
            protected Object clone() {
                try {
                    return super.clone();
                } catch (CloneNotSupportedException e) {
                    e.printStackTrace();
                }
                return null;
            }
        }
    }
  • 打印日誌

    2019-03-25 14:56:56.931 31454-31454/org.yczbj.ycrefreshview D/小楊逗比: lPeoples[0]=org.yczbj.ycrefreshview.MainActivity$People@46a2c18
    2019-03-25 14:56:56.931 31454-31454/org.yczbj.ycrefreshview D/小楊逗比: rPeoples[0]=org.yczbj.ycrefreshview.MainActivity$People@46a2c18
    2019-03-25 14:56:56.931 31454-31454/org.yczbj.ycrefreshview D/小楊逗比: 比較兩個對象true

8.2 集合深拷貝

  • 在某些特殊狀況下,若是須要實現集合的深拷貝,那就要建立一個新的集合,而後經過深拷貝原先集合中的每一個元素,將這些元素加入到新的集合當中。

    public static void test7() {
        ArrayList<People> lPeoples = new ArrayList<>();
        People people1 = new People();
        people1.holder = new People.Holder();
        lPeoples.add(people1);
        Log.d("小楊逗比", "lPeoples[0]=" + lPeoples.get(0));
        ArrayList<People> rPeoples = new ArrayList<>();
        for (People people : lPeoples) {
            rPeoples.add((People) people.clone());
        }
        Log.d("小楊逗比", "rPeoples[0]=" + rPeoples.get(0));
        boolean b = lPeoples.get(0).equals(rPeoples.get(0));
        Log.d("小楊逗比", "比較兩個對象" + b);
    }
    
    public static class People implements Cloneable {
    
        int age;
        Holder holder;
    
        @Override
        protected Object clone() {
            try {
                People people = (People) super.clone();
                people.holder = (People.Holder) this.holder.clone();
                return people;
            } catch (CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return null;
        }
    
        public static class Holder implements Cloneable {
    
            int holderValue;
    
            @Override
            protected Object clone() {
                try {
                    return super.clone();
                } catch (CloneNotSupportedException e) {
                    e.printStackTrace();
                }
                return null;
            }
        }
    }
  • 打印日誌

    2019-03-25 15:00:54.610 31670-31670/org.yczbj.ycrefreshview D/小楊逗比: lPeoples[0]=org.yczbj.ycrefreshview.MainActivity$People@46a2c18
    2019-03-25 15:00:54.610 31670-31670/org.yczbj.ycrefreshview D/小楊逗比: rPeoples[0]=org.yczbj.ycrefreshview.MainActivity$People@d344671
    2019-03-25 15:00:54.610 31670-31670/org.yczbj.ycrefreshview D/小楊逗比: 比較兩個對象false

其餘介紹

01.關於博客彙總連接

02.關於個人博客

相關文章
相關標籤/搜索