<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 淺拷貝和深拷貝的理解和實現(xiàn)方式

          共 11451字,需瀏覽 23分鐘

           ·

          2021-07-06 12:14

          點擊上方「Java有貨」關(guān)注我們


          技術(shù)交流群添加方式


          +



          添加小編微信:372787553,備注:進(jìn)群
          帶您進(jìn)入Java技術(shù)交流群

          簡介

          Java中的對象拷貝(Object Copy)指的是將一個對象的所有屬性(成員變量)拷貝到另一個有著相同類類型的對象中去。舉例說明:比如,對象A和對象B都屬于類S,具有屬性a和b。那么對對象A進(jìn)行拷貝操作賦值給對象B就是:B.a=A.a; B.b=A.b;在程序中拷貝對象是很常見的,主要是為了在新的上下文環(huán)境中復(fù)用現(xiàn)有對象的部分或全部 數(shù)據(jù)。

          Java中的對象拷貝主要分為:淺拷貝(Shallow Copy)、深拷貝(Deep Copy)。

          先介紹一點鋪墊知識:Java中的數(shù)據(jù)類型分為基本數(shù)據(jù)類型和引用數(shù)據(jù)類型。對于這兩種數(shù)據(jù)類型,在進(jìn)行賦值操作、用作方法參數(shù)或返回值時,會有值傳遞和引用(地址)傳遞的差別。

          淺拷貝(Shallow Copy)

          ①對于數(shù)據(jù)類型是基本數(shù)據(jù)類型的成員變量,淺拷貝會直接進(jìn)行值傳遞,也就是將該屬性值復(fù)制一份給新的對象。因為是兩份不同的數(shù)據(jù),所以對其中一個對象的該成員變量值進(jìn)行修改,不會影響另一個對象拷貝得到的數(shù)據(jù)。

          ②對于數(shù)據(jù)類型是引用數(shù)據(jù)類型的成員變量,比如說成員變量是某個數(shù)組、某個類的對象等,那么淺拷貝會進(jìn)行引用傳遞,也就是只是將該成員變量的引用值(內(nèi)存地址)復(fù)制一份給新的對象。因為實際上兩個對象的該成員變量都指向同一個實例。在這種情況下,在一個對象中修改該成員變量會影響到另一個對象的該成員變量值。

          具體模型如圖所示:可以看到基本數(shù)據(jù)類型的成員變量,對其值創(chuàng)建了新的拷貝。而引用數(shù)據(jù)類型的成員變量的實例仍然是只有一份,兩個對象的該成員變量都指向同一個實例。


          淺拷貝的實現(xiàn)方式

          • 通過拷貝構(gòu)造方法實現(xiàn)淺拷貝:

          拷貝構(gòu)造方法指的是該類的構(gòu)造方法參數(shù)為該類的對象。使用拷貝構(gòu)造方法可以很好地完成淺拷貝,直接通過一個現(xiàn)有的對象創(chuàng)建出與該對象屬性相同的新的對象。

          代碼參考如下

          /* 拷貝構(gòu)造方法實現(xiàn)淺拷貝 */
          public class CopyConstructor {
            public static void main(String[] args) {
                Age a=new Age(20);
                Person p1=new Person(a,"搖頭耶穌");
                Person p2=new Person(p1);
                System.out.println("p1是"+p1);
                System.out.println("p2是"+p2);
                //修改p1的各屬性值,觀察p2的各屬性值是否跟隨變化
                p1.setName("小傻瓜");
                a.setAge(99);
                System.out.println("修改后的p1是"+p1);
                System.out.println("修改后的p2是"+p2);
            }
          }

          class Person{
            //兩個屬性值:分別代表值傳遞和引用傳遞
            private Age age;
            private String name;
            public Person(Age age,String name) {
                this.age=age;
                this.name=name;
            }
            //拷貝構(gòu)造方法
            public Person(Person p) {
                this.name=p.name;
                this.age=p.age;
            }
             
            public void setName(String name) {
                this.name=name;
            }
             
            public String toString() {
                return this.name+" "+this.age;
            }
          }

          class Age{
            private int age;
            public Age(int age) {
                this.age=age;
            }
            //get set
          }

          運(yùn)行結(jié)果為:

          p1是搖頭耶穌 20

          p2是搖頭耶穌 20修改后的

          p1是小傻瓜 99修改后的

          p2是搖頭耶穌 99

          結(jié)果分析:這里對Person類選擇了兩個具有代表性的屬性值:一個是引用傳遞類型;另一個是字符串類型(屬于常量)。

          通過拷貝構(gòu)造方法進(jìn)行了淺拷貝,各屬性值成功復(fù)制。其中,p1值傳遞部分的屬性值發(fā)生變化時,p2不會隨之改變;而引用傳遞部分屬性值發(fā)生變化時,p2也隨之改變。

          要注意:如果在拷貝構(gòu)造方法中,對引用數(shù)據(jù)類型變量逐一開辟新的內(nèi)存空間,創(chuàng)建新的對象,也可以實現(xiàn)深拷貝。而對于一般的拷貝構(gòu)造,則一定是淺拷貝。

          • 通過重寫clone()方法進(jìn)行淺拷貝:

          Object類是類結(jié)構(gòu)的根類,其中有一個方法為protected Object clone() throws CloneNotSupportedException,這個方法就是進(jìn)行的淺拷貝。有了這個淺拷貝模板,我們可以通過調(diào)用clone()方法來實現(xiàn)對象的淺拷貝。但是需要注意:1、Object類雖然有這個方法,但是這個方法是受保護(hù)的(被protected修飾),所以我們無法直接使用。2、使用clone方法的類必須實現(xiàn)Cloneable接口,否則會拋出異常CloneNotSupportedException。對于這兩點,我們的解決方法是,在要使用clone方法的類中重寫clone()方法,通過super.clone()調(diào)用Object類中的原clone方法。

          參考代碼如下:對Student類的對象進(jìn)行拷貝,直接重寫clone()方法,通過調(diào)用clone方法即可完成淺拷貝。

          /* clone方法實現(xiàn)淺拷貝 */
          public class ShallowCopy {
            public static void main(String[] args) {
                Age a=new Age(20);
                Student stu1=new Student("搖頭耶穌",a,175);
                 
                //通過調(diào)用重寫后的clone方法進(jìn)行淺拷貝
                Student stu2=(Student)stu1.clone();
                System.out.println(stu1.toString());
                System.out.println(stu2.toString());
                 
                //嘗試修改stu1中的各屬性,觀察stu2的屬性有沒有變化
                stu1.setName("大傻子");
                //改變age這個引用類型的成員變量的值
                a.setAge(99);
                //stu1.setaAge(new Age(99));   使用這種方式修改age屬性值的話,stu2是不會跟著改變的。因為創(chuàng)建了一個新的Age類對象而不是改變原對象的實例值
                stu1.setLength(216);
                System.out.println(stu1.toString());
                System.out.println(stu2.toString());
            }
          }

          /*
          * 創(chuàng)建年齡類
          */
          class Age{
            //年齡類的成員變量(屬性)
            private int age;
            //構(gòu)造方法
            public Age(int age) {
                this.age=age;
            }
          //get set
          }
          /*
          * 創(chuàng)建學(xué)生類
          */
          class Student implements Cloneable{
            //學(xué)生類的成員變量(屬性),其中一個屬性為類的對象
            private String name;
            private Age aage;
            private int length;
            //構(gòu)造方法,其中一個參數(shù)為另一個類的對象
            public Student(String name,Age a,int length) {
                this.name=name;
                this.aage=a;
                this.length=length;
            }
            //get set
            //設(shè)置輸出的字符串形式
            public String toString() {
                return "姓名是:"+this.getName()+", 年齡為:"+this.getaAge().toString()+", 長度是:"+this.getLength();
            }
            //重寫Object類的clone方法
            public Object clone() {
                Object obj=null;
                //調(diào)用Object類的clone方法,返回一個Object實例
                try {
                    obj= super.clone();
                } catch (CloneNotSupportedException e) {
                    e.printStackTrace();
                }
                return obj;
            }
          }

          運(yùn)行結(jié)果如下:

          姓名是:搖頭耶穌, 年齡為:20, 長度是:175

          姓名是:搖頭耶穌, 年齡為:20, 長度是:175

          姓名是:大傻子, 年齡為:99, 長度是:216

          姓名是:搖頭耶穌, 年齡為:99, 長度是:175

          其中:Student類的成員變量我有代表性地設(shè)置了三種:基本數(shù)據(jù)類型的成員變量length,引用數(shù)據(jù)類型的成員變量aage和字符串String類型的name.

          分析結(jié)果可以驗證:

          基本數(shù)據(jù)類型是值傳遞,所以修改值后不會影響另一個對象的該屬性值;

          引用數(shù)據(jù)類型是地址傳遞(引用傳遞),所以修改值后另一個對象的該屬性值會同步被修改。

          String類型非常特殊,所以我額外設(shè)置了一個字符串類型的成員變量來進(jìn)行說明。首先,String類型屬于引用數(shù)據(jù)類型,不屬于基本數(shù)據(jù)類型,但是String類型的數(shù)據(jù)是存放在常量池中的,也就是無法修改的!也就是說,當(dāng)我將name屬性從“搖頭耶穌”改為“大傻子"后,并不是修改了這個數(shù)據(jù)的值,而是把這個數(shù)據(jù)的引用從指向”搖頭耶穌“這個常量改為了指向”大傻子“這個常量。在這種情況下,另一個對象的name屬性值仍然指向”搖頭耶穌“不會受到影響。


          深拷貝

          首先介紹對象圖的概念。設(shè)想一下,一個類有一個對象,其成員變量中又有一個對象,該對象指向另一個對象,另一個對象又指向另一個對象,直到一個確定的實例。這就形成了對象圖。那么,對于深拷貝來說,不僅要復(fù)制對象的所有基本數(shù)據(jù)類型的成員變量值,還要為所有引用數(shù)據(jù)類型的成員變量申請存儲空間,并復(fù)制每個引用數(shù)據(jù)類型成員變量所引用的對象,直到該對象可達(dá)的所有對象。也就是說,對象進(jìn)行深拷貝要對整個對象圖進(jìn)行拷貝!

          簡單地說,深拷貝對引用數(shù)據(jù)類型的成員變量的對象圖中所有的對象都開辟了內(nèi)存空間;而淺拷貝只是傳遞地址指向,新的對象并沒有對引用數(shù)據(jù)類型創(chuàng)建內(nèi)存空間。

          深拷貝模型如圖所示:可以看到所有的成員變量都進(jìn)行了復(fù)制。

          因為創(chuàng)建內(nèi)存空間和拷貝整個對象圖,所以深拷貝相比于淺拷貝速度較慢并且花銷較大。

          深拷貝的實現(xiàn)方法主要有兩種:

          • 通過重寫clone方法來實現(xiàn)深拷貝

          與通過重寫clone方法實現(xiàn)淺拷貝的基本思路一樣,只需要為對象圖的每一層的每一個對象都實現(xiàn)Cloneable接口并重寫clone方法,最后在最頂層的類的重寫的clone方法中調(diào)用所有的clone方法即可實現(xiàn)深拷貝。簡單的說就是:每一層的每個對象都進(jìn)行淺拷貝=深拷貝。

          參考代碼如下:

          package linearList;
          /* 層次調(diào)用clone方法實現(xiàn)深拷貝 */
          public class DeepCopy {
            public static void main(String[] args) {
                Age a=new Age(20);
                Student stu1=new Student("搖頭耶穌",a,175);
                 
                //通過調(diào)用重寫后的clone方法進(jìn)行淺拷貝
                Student stu2=(Student)stu1.clone();
                System.out.println(stu1.toString());
                System.out.println(stu2.toString());
                System.out.println();
                 
                //嘗試修改stu1中的各屬性,觀察stu2的屬性有沒有變化
                stu1.setName("大傻子");
                //改變age這個引用類型的成員變量的值
                a.setAge(99);
                //stu1.setaAge(new Age(99));   使用這種方式修改age屬性值的話,stu2是不會跟著改變的。因為創(chuàng)建了一個新的Age類對象而不是改變原對象的實例值
                stu1.setLength(216);
                System.out.println(stu1.toString());
                System.out.println(stu2.toString());
            }
          }

          /*
          * 創(chuàng)建年齡類
          */
          class Age implements Cloneable{
            //年齡類的成員變量(屬性)
            private int age;
            //構(gòu)造方法
            public Age(int age) {
                this.age=age;
            }
          // get set
            //重寫Object的clone方法
            public Object clone() {
                Object obj=null;
                try {
                    obj=super.clone();
                } catch (CloneNotSupportedException e) {
                    e.printStackTrace();
                }
                return obj;
            }
          }
          /*
          * 創(chuàng)建學(xué)生類
          */
          class Student implements Cloneable{
            //學(xué)生類的成員變量(屬性),其中一個屬性為類的對象
            private String name;
            private Age aage;
            private int length;
            //構(gòu)造方法,其中一個參數(shù)為另一個類的對象
            public Student(String name,Age a,int length) {
                this.name=name;
                this.aage=a;
                this.length=length;
            }
            // get set
            public String toString() {
                return "姓名是:"+this.getName()+", 年齡為:"+this.getaAge().toString()+", 長度是:"+this.getLength();
            }
            //重寫Object類的clone方法
            public Object clone() {
                Object obj=null;
                //調(diào)用Object類的clone方法——淺拷貝
                try {
                    obj= super.clone();
                } catch (CloneNotSupportedException e) {
                    e.printStackTrace();
                }
                //調(diào)用Age類的clone方法進(jìn)行深拷貝
                //先將obj轉(zhuǎn)化為學(xué)生類實例
                Student stu=(Student)obj;
                //學(xué)生類實例的Age對象屬性,調(diào)用其clone方法進(jìn)行拷貝
                stu.aage=(Age)stu.getaAge().clone();
                return obj;
            }
          }

          運(yùn)行結(jié)果如下:

          姓名是:搖頭耶穌, 年齡為:20, 長度是:175

          姓名是:搖頭耶穌, 年齡為:20, 長度是:175

          姓名是:大傻子, 年齡為:99, 長度是:216

          姓名是:搖頭耶穌, 年齡為:20, 長度是:175

          分析結(jié)果可以驗證:進(jìn)行了深拷貝之后,無論是什么類型的屬性值的修改,都不會影響另一個對象的屬性值。

          • 通過對象序列化實現(xiàn)深拷貝

          雖然層次調(diào)用clone方法可以實現(xiàn)深拷貝,但是顯然代碼量實在太大。特別對于屬性數(shù)量比較多、層次比較深的類而言,每個類都要重寫clone方法太過繁瑣。

          將對象序列化為字節(jié)序列后,默認(rèn)會將該對象的整個對象圖進(jìn)行序列化,再通過反序列即可完美地實現(xiàn)深拷貝。

          參考代碼如下:

          import java.io.ByteArrayInputStream;
          import java.io.ByteArrayOutputStream;
          import java.io.IOException;
          import java.io.ObjectInputStream;
          import java.io.ObjectOutputStream;
          import java.io.Serializable;

          /* 通過序列化實現(xiàn)深拷貝 */
          public class DeepCopyBySerialization {
            public static void main(String[] args) throws IOException, ClassNotFoundException {
                Age a=new Age(20);
                Student stu1=new Student("搖頭耶穌",a,175);
                //通過序列化方法實現(xiàn)深拷貝
                ByteArrayOutputStream bos=new ByteArrayOutputStream();
                ObjectOutputStream oos=new ObjectOutputStream(bos);
                oos.writeObject(stu1);
                oos.flush();
                ObjectInputStream ois=new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
                Student stu2=(Student)ois.readObject();
                System.out.println(stu1.toString());
                System.out.println(stu2.toString());
                System.out.println();
                //嘗試修改stu1中的各屬性,觀察stu2的屬性有沒有變化
                stu1.setName("大傻子");
                //改變age這個引用類型的成員變量的值
                a.setAge(99);
                stu1.setLength(216);
                System.out.println(stu1.toString());
                System.out.println(stu2.toString());
            }
          }

          /*
          * 創(chuàng)建年齡類
          */
          class Age implements Serializable{
            //年齡類的成員變量(屬性)
            private int age;
            //構(gòu)造方法
            public Age(int age) {
                this.age=age;
            }
           
          }
          /*
          * 創(chuàng)建學(xué)生類
          */
          class Student implements Serializable{
            //學(xué)生類的成員變量(屬性),其中一個屬性為類的對象
            private String name;
            private Age aage;
            private int length;
            //構(gòu)造方法,其中一個參數(shù)為另一個類的對象
            public Student(String name,Age a,int length) {
                this.name=name;
                this.aage=a;
                this.length=length;
            }
            //get set....
            //設(shè)置輸出的字符串形式
            public String toString() {
                return "姓名是:"+this.getName()+", 年齡為:"+this.getaAge().toString()+", 長度是:"+this.getLength();
            }
          }

          運(yùn)行結(jié)果為:

          姓名是:搖頭耶穌, 年齡為:20, 長度是:175

          姓名是:搖頭耶穌, 年齡為:20, 長度是:175

          姓名是:大傻子,     年齡為:99, 長度是:216

          姓名是:搖頭耶穌, 年齡為:20, 長度是:175

          可以通過很簡潔的代碼即可完美實現(xiàn)深拷貝。不過要注意的是,如果某個屬性被transient修飾,那么該屬性就無法被拷貝了。 以上是淺拷貝的深拷貝的區(qū)別和實現(xiàn)方式。

          瀏覽 55
          點贊
          評論
          收藏
          分享

          手機(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>
                  无码区一区二区三区 | 亚洲九九九九九 | 欧美成人性爱在线视频 | 国产午夜精品理论 | 色婷婷丁香五月 |