<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          淺拷貝 vs 深拷貝

          共 9410字,需瀏覽 19分鐘

           ·

          2021-08-20 11:35

          Hey guys ,這里是程序員cxuan,歡迎你收看我最新一期的文章。這篇文章我們來聊聊淺拷貝和深拷貝那些事兒。

          Java 對象拷貝是為對象賦值的一種方式,簡單來說就是創(chuàng)建一個和原對象相同的對象,新創(chuàng)建的對象是原對象的一個副本,面試官賊拉喜歡在面試的時候問一問你淺拷貝和深拷貝的原理。因為它涉及到對象的引用關(guān)系,涉及到 Java 是傳值還是傳遞引用關(guān)系,這通常是面試的重點。所以在聊深拷貝和淺拷貝之前,我們先來聊一聊引用關(guān)系。

          關(guān)于引用

          在 Java 中,除了基本數(shù)據(jù)類型(四類八種數(shù)據(jù)類型)之外,還存在引用數(shù)據(jù)類型。一般使用 = 號做賦值操作的時候,對于基本數(shù)據(jù)類型,實際上是拷貝的它的值,但是對于對象而言,其實賦值的只是這個對象的引用,也就是將原對象的引用傳遞過去,但是他們實際上還是指向的同一個對象。如下代碼所示

          public class Food{

              String name;
              int num;
              String taste;

              constructor()
            get and set()
              toString()
          }

          測試類:

          public static void main(String[] args) {

            int i1 = 10;
            int i2 = i1; // 基本數(shù)據(jù)類型的拷貝,拷貝值
            System.out.println("i2 = " + i2);

            Food milk = new Food("milk",1,"fragrance");
            Food food = milk; 
            System.out.printf("food = " + food);
            System.out.println("milk = " + milk); // milk 和 food 都指向同一個堆內(nèi)存對象
          }

          如果用圖表示的話,應(yīng)該是下面這樣的:

          不用糾結(jié) Java 中到底是值傳遞還是引用傳遞這種無意義的爭論中,你只要記得對于基本數(shù)據(jù)類型,傳遞的是數(shù)據(jù)類型的值,而對于引用類型來說,傳遞的是對象的引用,也就是對象的地址就可以了。

          關(guān)于淺拷貝和深拷貝

          淺拷貝和深拷貝其實就是在引用的這個基礎(chǔ)上來做區(qū)分的,如果在拷貝的時候,只對基本數(shù)據(jù)類型進(jìn)行拷貝,對引用數(shù)據(jù)類型只是進(jìn)行了引用的傳遞,沒有真正的創(chuàng)建一個新的對象,這種拷貝方式就認(rèn)為是淺拷貝。反之,在對引用數(shù)據(jù)類型進(jìn)行拷貝的時候,創(chuàng)建了一個新的對象,并且復(fù)制其內(nèi)的成員變量,這種拷貝方式就被認(rèn)為是深拷貝

          淺拷貝

          那么如何實現(xiàn)淺拷貝(Shallow copy)呢?很簡單,就是在需要拷貝的類上實現(xiàn) Cloneable 接口并重寫其 clone() 方法就可以了。

          下面我們對 Food 類進(jìn)行修改,我們讓他實現(xiàn) Cloneable 接口,并重寫 clone() 方法。

          public class Food implements Cloneable{
            
            ...
            @Override
            protected Object clone() throws CloneNotSupportedException {
              return super.clone();
            }      
            ...
          }

          然后在測試類中的代碼如下

          Food milk = new Food("milk",1,"fragrance");
          Food food = (Food)milk.clone();
          System.out.println("milk = " + milk);
          System.out.println("food = " + food);

          可以看到,現(xiàn)在的 food 對象是由 milk 對象拷貝出來的,那么此時的 food 對象和 milk 對象是同一個對象嗎?我們通過打印,可以看到這兩個對象的原生 hashcode

          milk = com.cxuan.objectclone.Food@3cd1a2f1
          food = com.cxuan.objectclone.Food@4d7e1886

          可以發(fā)現(xiàn),food 和 milk 并不是同一個對象,那 milk 中還有三個屬性值,這三個屬性值在 food 中是不是也一樣呢?為了驗證這個猜想,我們重寫了 toString 方法。

          @Override
          public String toString() {
            return "Food{" +
              "name='" + name + '\'' +
              ", num=" + num +
              ", taste='" + taste + '\'' +
              '}';
          }

          然后再次打印 food 和 milk ,可以觀察到如下結(jié)果

          milk = Food{name='milk', num=1, taste='fragrance'}
          food = Food{name='milk', num=1, taste='fragrance'}

          嗯哼,雖然看起來"cxuan 哥"和"cuan 哥"是兩種完全不同的稱呼!但是他們卻有一種共同的能力:寫作

          我們還是通過圖示來說明一下:

          這幅圖看出門道了么?在堆區(qū)分別出現(xiàn)了兩個 Food 對象,這同時表明 clone 方法會重新創(chuàng)建一個對象并為其分配一塊內(nèi)存區(qū)域;雖然出現(xiàn)了兩個對象,但是兩個對象中的屬性值是一樣的,這也是換湯不換藥,雖然湯和藥是不同的東西(對象),但是他們都溶于水(屬性值)。

          深拷貝

          雖然淺拷貝是一種換湯不換藥的說法,但是在 Java 世界中還是有一種說法是。。。。。。是啥來著?

          詞窮了。。。。。。

          哦對,還有一種改頭換面的形式,它就是我們所熟悉的深拷貝(Deep copy),先來拋出一下深拷貝的定義:在進(jìn)行對象拷貝的基礎(chǔ)上,對對象的成員變量也依次拷貝的方式被稱為深拷貝

          哈哈哈哈,這故作高深的深拷貝原來就是在淺拷貝的基礎(chǔ)上再復(fù)制一下它的屬性值啊,我還以為是啥高深的東西呢!上代碼!

          我們先增加一個飲品類 Drink 。

          public class Drink implements Cloneable {

              String name;

              get and set()

              @Override
              protected Object clone() throws CloneNotSupportedException 
          {
                  return super.clone();
              }

              toString()
          }

          然后更改一下 Food 類,因為 Drink 也算是 Food ,所以我們在 Food 類中增加對 Drink 的引用,然后再修改 get set 、toString 、clone 、構(gòu)造方法,修改后的 Food 類代碼如下

          public class Food implements Cloneable{

              String name;
              int num;
              String taste;
              Drink drink;

              public Food(String name, int num, String taste,Drink drink) {
                  this.name = name;
                  this.num = num;
                  this.taste = taste;
                  this.drink = drink;
              }

              get and set...

              @Override
              protected Object clone() throws CloneNotSupportedException {
                  Food food = (Food)super.clone();
                  food.drink = (Drink) drink.clone();
                  return super.clone();
              }

              @Override
              public String toString() {
                  return "Food{" +
                          "name='" + name + '\'' +
                          ", num=" + num +
                          ", taste='" + taste + '\'' +
                          ", drink=" + drink +
                          '}';
              }
          }

          可以看到最大的改變是 clone 方法,我們在 clone 方法中,實現(xiàn)了對 Food 對象的拷貝,同時也實現(xiàn)了對 Drink 對象的拷貝,這就是我們上面所說的復(fù)制對象并復(fù)制對象的成員變量

          然后我們進(jìn)行一下 Deep Copy的測試:

          public static void main(String[] args) throws CloneNotSupportedException {

            Drink drink = new Drink("milk");
            Food food = new Food("humberge",1,"fragrance",drink);
            Food foodClone = (Food)food.clone();
            Drink tea = new Drink("tea");
            food.setDrink(tea);
            System.out.println("food = " + food);
            System.out.println("foodClone = " + foodClone.getDrink());

          }

          運行完成后的輸出結(jié)果如下:

          food = Food{name='humberge', num=1, taste='fragrance', drink=Drink{name='tea'}}
          foodClone = Drink{name='milk'}

          可以看到,我們把 foodClone 拷貝出來之后,修改 food 中的 drink 變量,卻不會對 foodClone 造成改變,這就說明 foodClone 已經(jīng)成功實現(xiàn)了深拷貝。

          用圖示表示的話,應(yīng)該是下面這樣的:

          這是深拷貝之后的內(nèi)存分配圖,現(xiàn)在可以看到,food 和 foodClone 完全是兩個不同的對象,它們之間不存在紐帶關(guān)系。

          我們上面主要探討實現(xiàn)對象拷貝的方式是對象實現(xiàn) Cloneable 接口,并且調(diào)用重寫之后的 clone 方法,在 Java 中,還有一種實現(xiàn)對象拷貝的方式是使用 序列化

          序列化

          使用序列化的方式主要是使用 Serializable 接口,這種方式還以解決多層拷貝的問題,多層拷貝就是引用類型里面又有引用類型,層層嵌套下去。使用 Serializable 的關(guān)鍵代碼如下

          public Person clone() {
            Person person = null;
            try {
              ByteArrayOutputStream baos = new ByteArrayOutputStream();
              ObjectOutputStream oos = new ObjectOutputStream(baos);
              oos.writeObject(this);
              // 將流序列化成對象
              ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
              ObjectInputStream ois = new ObjectInputStream(bais);
              person = (Person) ois.readObject();
            } catch (IOException e) {
              e.printStackTrace();
            } catch (ClassNotFoundException e) {
              e.printStackTrace();
            }
            return person;
          }

          使用序列化可以實現(xiàn)深拷貝,它的原理是將二進(jìn)制字節(jié)流內(nèi)容寫到一個文本或字節(jié)數(shù)組,然后是從這個文本或者字節(jié)數(shù)組中讀取數(shù)據(jù),原對象寫入這個文本或者字節(jié)數(shù)組后再拷貝給 clone 對象,原對象的修改不會影響 clone 對象,因為 clone 對象是從文本或者字節(jié)數(shù)組中讀取的。

          如何選擇拷貝方式

          到現(xiàn)在我們已經(jīng)把淺拷貝和深拷貝都介紹完了,那么如何選擇淺拷貝和深拷貝呢?下面是幾點注意事項??

          • 如果對象的屬性都是基本數(shù)據(jù)類型,那么可以使用淺拷貝。

          • 如果對象有引用類型,那就要基于具體的需求來選擇淺拷貝還是深拷貝。

          • 如果對象嵌套層數(shù)比較多,推薦使用 Serializable 接口實現(xiàn)深拷貝。

          • 如果對象引用任何時候都不會被改變,那么沒必要使用深拷貝,只需要使用淺拷貝就行了。如果對象引用經(jīng)常改變,那么就要使用深拷貝。沒有一成不變的規(guī)則,一切都取決于具體需求。

          其他拷貝方式

          除了對象的拷貝,Java 中還提供了其他的拷貝方式

          比如數(shù)組的拷貝,你可以使用 Arrays.copyof 實現(xiàn)數(shù)組拷貝,還可以使用默認(rèn)的 clone 進(jìn)行拷貝,不過這兩者都是淺拷貝。

          public void test() {
              int[] lNumbers1 = new int[5];
              int[] rNumbers1 = Arrays.copyOf(lNumbers1, lNumbers1.length);

              int[] lNumbers2 = new int[5];
              int[] rNumbers2 = lNumbers2.clone();
          }

          除了基本數(shù)組數(shù)據(jù)類型之外的拷貝,還有對象的拷貝,不過用法基本是一樣的。

          集合也可以實現(xiàn)拷貝,因為集合的底層就使用的是數(shù)組,所以用法也是一樣的。

          一些說明

          針對 Cloneable 接口,有下面三點使用說明

          • 如果類實現(xiàn)了 Cloneable 接口,再調(diào)用 Object 的 clone() 方法可以合法地對該類實例進(jìn)行按字段復(fù)制

          • 如果在沒有實現(xiàn) Cloneable 接口的實例上調(diào)用 Object 的 clone() 方法,則會導(dǎo)致拋出CloneNotSupporteddException

          • 實現(xiàn)此接口的類應(yīng)該使用公共方法重寫 Object 的clone() 方法,因為 Object 的 clone() 方法是一個受保護(hù)的方法。





           往期推薦 

          ??

          滬漂程序媛妹子的一天...

          如何評價《Java 并發(fā)編程藝術(shù)》這本書?

          在騰訊工作是一種怎樣的體驗?

          圖解 HTTP 連接管理

          我真不想學(xué) happens - before 了!

          42 張圖帶你擼完 MySQL  優(yōu)化

          瀏覽 28
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  91亚瑟| 9·1樱桃免费观看网站 | 日本人妻三级 | 91欧美在线播放 | 中文字幕有码在线播放 |