<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>

          Java 輕松理解深拷貝與淺拷貝

          共 10965字,需瀏覽 22分鐘

           ·

          2021-05-02 07:00

          點(diǎn)擊上方藍(lán)色字體,選擇“標(biāo)星公眾號”

          優(yōu)質(zhì)文章,第一時(shí)間送達(dá)

            作者 |  aduner

          來源 |  urlify.cn/yuyYra

          前言

          本文代碼中有用到一些注解,主要是Lombok與junit用于簡化代碼。

          主要是看到一堆代碼會(huì)很亂,這樣理解更清晰。如果沒用過不用太過糾結(jié)。

          對象的拷貝(克隆)是一個(gè)非常高頻的操作,主要有以下三種方式:

          • 直接賦值

          • 拷貝:

            • 淺拷貝

            • 深拷貝

          因?yàn)镴ava沒有指針的概念,或者說是不需要我們?nèi)ゲ傩模@讓我們省去了很多麻煩,但相應(yīng)的,對于對象的引用、拷貝有時(shí)候就會(huì)有些懵逼,藏下一些很難發(fā)現(xiàn)的bug。

          為了避免這些bug,理解這三種操作的作用與區(qū)別就是關(guān)鍵。

          直接賦值

          用等于號直接賦值是我們平時(shí)最常用的一種方式。

          它的特點(diǎn)就是直接引用等號右邊的對象

          先來看下面的例子

          先創(chuàng)建一個(gè)Person

          @Data
          @AllArgsConstructor 
          @ToString
          public class Person{
              private String name;
              private int age;
              private Person friend;
          }

          測試

          @Test
          public void test() {
            Person friend =new Person("老王",30,null);
            Person person1 = new Person("張三", 20, null);
            Person person2 = person1;
            System.out.println("person1: " + person1);
            System.out.println("person2: " + person2 + "\n");
            person1.setName("張四");
            person1.setAge(25);
            person1.setFriend(friend);
            System.out.println("person1: " + person1);
            System.out.println("person2: " + person2);
          }

          結(jié)果

          person1: Person(name=張三, age=20, friend=null)
          person2: Person(name=張三, age=20, friend=null)

          person1: Person(name=張四, age=25, friend=Person(name=老王, age=30, friend=null))
          person2: Person(name=張四, age=25, friend=Person(name=老王, age=30, friend=null))

          分析:

          可以看到通過直接賦值進(jìn)行拷貝,其實(shí)就只是單純的對前對象進(jìn)行引用。

          如果這些對象都是基礎(chǔ)對象當(dāng)然沒什么問題,但是如果對象進(jìn)行操作,相當(dāng)于兩個(gè)對象同屬一個(gè)實(shí)例

          拷貝

          直接賦值雖然方便,但是很多時(shí)候并不是我們想要的結(jié)果,很多時(shí)候我們需要的是兩個(gè)看似一樣但是完全獨(dú)立的兩個(gè)對象。

          這種時(shí)候我們就需要用到一個(gè)方法clone()

          clone()并不是一個(gè)可以直接使用的方法,需要先實(shí)現(xiàn)Cloneable接口,然后重寫它才能使用。

          protected native Object clone() throws CloneNotSupportedException;

          clone()方法被native關(guān)鍵字修飾,native關(guān)鍵字說明其修飾的方法是一個(gè)原生態(tài)方法,方法對應(yīng)的實(shí)現(xiàn)不是在當(dāng)前文件,而是系統(tǒng)或者其他語言來實(shí)現(xiàn)。

          淺拷貝

          淺拷貝可以實(shí)現(xiàn)對象克隆,但是存在一些缺陷。

          定義:

          • 如果原型對象的成員變量是值類型,將復(fù)制一份給克隆對象,也就是在堆中擁有獨(dú)立的空間;

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

          舉例

          光看定義不太好一下子理解,上代碼看例子。

          我們先來修改一下Person類,實(shí)現(xiàn)Cloneable接口,重寫clone()方法,其實(shí)很簡單,只需要用super調(diào)用一下即可

          @Data
          @AllArgsConstructor
          @ToString
          public class Person implements Cloneable {
              private String name;
              private int age;
              private Friend friend;
              @Override
              public Object clone() {
                  try {
                      return super.clone();
                  } catch (CloneNotSupportedException e) {
                      e.printStackTrace();
                  }
                  return null;
              }
          }

          -------
            
          @Data
          @AllArgsConstructor
          public class Friend {
              private String Name;
          }

          測試

          @Test
          public void test() {
            Person person1 = new Person("張三", 20, "老王");
            Person person2 = (Person) person1.clone();

            System.out.println("person1: " + person1);
            System.out.println("person2: " + person2 + "\n");
            person1.setName("張四");
            person1.setAge(25);
            person1.setFriend("小王");
            System.out.println("person1: " + person1);
            System.out.println("person2: " + person2);
          }

          結(jié)果

          person1: Person(name=張三, age=20, friend=Friend(Name=老王))
          person2: Person(name=張三, age=20, friend=Friend(Name=老王))

          person1: Person(name=張四, age=25, friend=Friend(Name=小王))
          person2: Person(name=張三, age=20, friend=Friend(Name=小王))

          可以看到,name age基本對象屬性并沒改變,而friend引用對象熟悉變了。

          原理

          Java淺拷貝的原理其實(shí)是把原對象的各個(gè)屬性的地址拷貝給新對象。

          注意我說的是各個(gè)屬性就算是基礎(chǔ)對象屬性其實(shí)也是拷貝的地址

          你可能有點(diǎn)暈了,都是拷貝了地址,為什么修改了 person1 對象的 name age 屬性值,person2 對象的 name age 屬性值沒有改變呢?

          我們一步步來,拿name屬性來說明:

          1. String、Integer 等包裝類都是不可變的對象

          2. 當(dāng)需要修改不可變對象的值時(shí),需要在內(nèi)存中生成一個(gè)新的對象來存放新的值

          3. 然后將原來的引用指向新的地址

          4. 我們修改了 person1 對象的 name 屬性值,person1 對象的 name 字段指向了內(nèi)存中新的 String 對象

          5. 我們并沒有改變 person2 對象的 name 字段的指向,所以 person2 對象的 name 還是指向內(nèi)存中原來的 String 地址

          看圖

          這個(gè)圖已經(jīng)很清晰的展示了其中的過程,因?yàn)?/span>person1 對象改變friend時(shí)是改變的引用對象的屬性,并不是新建立了一個(gè)對象進(jìn)行替換,原本老王的消失了,變成了小王。所以person2也跟著改變了。

          深拷貝

          深拷貝就是我們拷貝的初衷了,無論是值類型還是引用類型都會(huì)完完全全的拷貝一份,在內(nèi)存中生成一個(gè)新的對象。

          拷貝對象和被拷貝對象沒有任何關(guān)系,互不影響。

          深拷貝相比于淺拷貝速度較慢并且花銷較大。

          簡而言之,深拷貝把要復(fù)制的對象所引用的對象都復(fù)制了一遍。

          因?yàn)镴ava本身的特性,對于不可變的基本值類型,無論如何在內(nèi)存中都是只有一份的。

          所以對于不可變的基本值類型,深拷貝跟淺拷貝一樣,不過并不影響什么。

          實(shí)現(xiàn):

          想要實(shí)現(xiàn)深拷貝并不難,只需要在淺拷貝的基礎(chǔ)上進(jìn)行一點(diǎn)修改即可。

          • 給friend添加一個(gè)clone()方法。

          • Person類的clone()方法調(diào)用friendclone()方法,將friend也復(fù)制一份即可。

          @Data
          @ToString
          public class Person implements Cloneable {
              private String name;
              private int age;
              private Friend friend;

              public Person(String name, int age, String friend) {
                  this.name = name;
                  this.age = age;
                  this.friend = new Friend(friend);
              }

              public void setFriend(String friend) {
                  this.friend.setName(friend);
              }

              @Override
              public Object clone() {
                  try {
                      Person person = (Person)super.clone();
                      person.friend = (Friend) friend.clone();
                      return person;
                  } catch (CloneNotSupportedException e) {
                      e.printStackTrace();
                  }
                  return null;
              }
          }

          ------
            
          @Data
          @AllArgsConstructor
          public class Friend implements Cloneable{
              private String Name;

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

          測試

          @Test
          public void test() {
          Person person1 = new Person("張三", 20, "老王");
          Person person2 = (Person) person1.clone();

          System.out.println("person1: " + person1);
          System.out.println("person2: " + person2 + "\n");
          person1.setName("張四");
          person1.setAge(25);
          person1.setFriend("小王");
          System.out.println("person1: " + person1);
          System.out.println("person2: " + person2);
          }

          結(jié)果

          person1: Person(name=張三, age=20, friend=Friend(Name=老王))
          person2: Person(name=張三, age=20, friend=Friend(Name=老王))

          person1: Person(name=張四, age=25, friend=Friend(Name=小王))
          person2: Person(name=張三, age=20, friend=Friend(Name=老王))

          分析:

          可以看到這次是真正的完全獨(dú)立了起來。

          需要注意的是,如果Friend類本身也存在引用類型,則需要在Friend類中的clone(),也去調(diào)用其引用類型的clone()方法,就如是Person類中那樣,對!就是套娃!

          所以對于存在多層依賴關(guān)系的對象,實(shí)現(xiàn)Cloneable接口重寫clone()方法就顯得有些笨拙了。

          這里我們在介紹一種方法:利用序列化實(shí)現(xiàn)深拷貝

          Serializable 實(shí)現(xiàn)深拷貝

          修改PersonFriend,實(shí)現(xiàn)Serializable接口

          @Data
          @ToString
          public class Person implements Serializable {
              // ......同之前
              public Object deepClone() throws Exception {
                  // 序列化
                  ByteArrayOutputStream bos = new ByteArrayOutputStream();
                  ObjectOutputStream oos = new ObjectOutputStream(bos);

                  oos.writeObject(this);

                  // 反序列化
                  ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
                  ObjectInputStream ois = new ObjectInputStream(bis);

                  return ois.readObject();
              }
          }

          ---
            
          @Data
          @AllArgsConstructor
          public class Friend implements Serializable {
              private String Name;
          }

          測試

          @Test
          public void test() {
              Person person1 = new Person("張三", 20, "老王");
              Person person2 = null;
              try {
                  person2 = (Person) person1.deepClone();
              } catch (Exception e) {
                  e.printStackTrace();
              }

              System.out.println("person1: " + person1);
              System.out.println("person2: " + person2 + "\n");
              person1.setName("張四");
              person1.setAge(25);
              person1.setFriend("小王");
              System.out.println("person1: " + person1);
              System.out.println("person2: " + person2);
          }

          結(jié)果

          person1: Person(name=張三, age=20, friend=Friend(Name=老王))
          person2: Person(name=張三, age=20, friend=Friend(Name=老王))

          person1: Person(name=張四, age=25, friend=Friend(Name=小王))
          person2: Person(name=張三, age=20, friend=Friend(Name=老王))

          只要將會(huì)被復(fù)制到的引用對象標(biāo)記Serializable接口,通過序列化到方式即可實(shí)現(xiàn)深拷貝。

          原理:

          對象被序列化成流后,因?yàn)閷懺诹骼锏氖?span style="margin-right: 3px;margin-left: 3px;">對象的一個(gè)拷貝,而原對象仍然存在于虛擬機(jī)里面

          通過反序列化就可以獲得一個(gè)完全相同的拷貝。

          利用這個(gè)特性就實(shí)現(xiàn)了對象的深拷貝。

          總結(jié)

          • 直接賦值是將新的對象指向原對象所指向的實(shí)例,所以一旦有所修改,兩個(gè)對象會(huì)一起變。

          • 淺拷貝是把原對象屬性的地址傳給新對象,對于不可變的基礎(chǔ)類型,實(shí)現(xiàn)了二者的分離,但對于引用對象,二者還是會(huì)一起改變。

          • 深拷貝是真正的完全拷貝,二者沒有關(guān)系。實(shí)現(xiàn)深拷貝時(shí)如果存在多層依賴關(guān)系,可以采用序列化的方式來進(jìn)行實(shí)現(xiàn)。

          對于Serializable接口、Cloneable接口,其實(shí)都是相當(dāng)于一個(gè)標(biāo)記,點(diǎn)進(jìn)去看源碼,其實(shí)他們是一個(gè)空接口。



          鋒哥最新SpringCloud分布式電商秒殺課程發(fā)布

          ??????

          ??長按上方微信二維碼 2 秒





          感謝點(diǎn)贊支持下哈 

          瀏覽 66
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評論
          圖片
          表情
          推薦
          點(diǎn)贊
          評論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                  麻豆一区 | 亲子乱伦一区二区三区 | 欧美日韩性爱一区二区 | 日韩免费黄色电影 | 影音先锋女人av鲁色资源网小说 |