www.久久久久|狼友网站av天堂|精品国产无码a片|一级av色欲av|91在线播放视频|亚洲无码主播在线|国产精品草久在线|明星AV网站在线|污污内射久久一区|婷婷综合视频网站

當前位置:首頁 > 公眾號精選 > 程序員小灰
[導讀]對象拷貝在我們?nèi)粘懘a的時候基本上是剛性需求,經(jīng)常遇到,只不過很多人天天忙于寫業(yè)務,忽視了一些細節(jié)問題和理解,有時候這方面一旦出了問題,就不太容易排查了。 所以本篇好好梳理一下。 注:本文已收錄于Github開源項目:github.com/hansonwang99/Java



對象拷貝在我們?nèi)粘懘a的時候基本上是剛性需求,經(jīng)常遇到,只不過很多人天天忙于寫業(yè)務,忽視了一些細節(jié)問題和理解,有時候這方面一旦出了問題,就不太容易排查了。

所以本篇好好梳理一下。

注:本文已收錄于Github開源項目:github.com/hansonwang99/JavaCollection,里面有詳細自學編程學習路線、面試題和面經(jīng)、編程資料及系列技術(shù)文章等,資源持續(xù)更新中...


值類型 vs 引用類型

這兩個概念的準確區(qū)分,對于深、淺拷貝問題的理解非常重要。

正如Java圣經(jīng)《Java編程思想》第二章的標題所言,在Java中一切都可以視為對象!

所以來到Java的世界,我們要習慣用引用去操作對象。在Java中,像數(shù)組、類Class、枚舉Enum、Integer包裝類等等,就是典型的引用類型,所以操作時一般來說采用的也是引用傳遞的方式;

但是Java的語言級基礎(chǔ)數(shù)據(jù)類型,諸如int這些基本類型,操作時一般采取的則是值傳遞的方式,所以有時候也稱它為值類型。

為了便于下文的講述和舉例,我們這里先定義兩個類:Student和Major,分別表示「學生」以及「所學的專業(yè)」,二者是包含關(guān)系:

// 學生的所學專業(yè) public class Major { private String majorName; // 專業(yè)名稱 private long majorId; // 專業(yè)代號 // ... 其他省略 ... }
// 學生 public class Student { private String name; // 姓名 private int age; // 年齡 private Major major; // 所學專業(yè) // ... 其他省略 ... }

賦值 vs 淺拷貝 vs 深拷貝

對象賦值

賦值是日常編程過程中最常見的操作,最簡單的比如:

Student codeSheep = new Student();
Student codePig = codeSheep;

嚴格來說,這種不能算是對象拷貝,因為拷貝的僅僅只是引用關(guān)系,并沒有生成新的實際對象:

淺拷貝

淺拷貝屬于對象克隆方式的一種,重要的特性體現(xiàn)在這個 「淺」 字上。

比如我們試圖通過studen1實例,拷貝得到student2,如果是淺拷貝這種方式,大致模型可以示意成如下所示的樣子:

很明顯,值類型的字段會復制一份,而引用類型的字段拷貝的僅僅是引用地址,而該引用地址指向的實際對象空間其實只有一份。

一圖勝前言,我想上面這個圖已經(jīng)表現(xiàn)得很清楚了。

深拷貝

深拷貝相較于上面所示的淺拷貝,除了值類型字段會復制一份,引用類型字段所指向的對象,會在內(nèi)存中也創(chuàng)建一個副本,就像這個樣子:

原理很清楚明了,下面來看看具體的代碼實現(xiàn)吧。


淺拷貝代碼實現(xiàn)

還以上文的例子來講,我想通過student1拷貝得到student2,淺拷貝的典型實現(xiàn)方式是:讓被復制對象的類實現(xiàn)Cloneable接口,并重寫clone()方法即可。

以上面的Student類拷貝為例:

public class Student implements Cloneable { private String name; // 姓名 private int age; // 年齡 private Major major; // 所學專業(yè) @Override public Object clone() throws CloneNotSupportedException { return super.clone();
    } // ... 其他省略 ... }

然后我們寫個測試代碼,一試便知:

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

        Major m = new Major("計算機科學與技術(shù)",666666);
        Student student1 = new Student( "CodeSheep", 18, m ); // 由 student1 拷貝得到 student2 Student student2 = (Student) student1.clone();

        System.out.println( student1 == student2 );
        System.out.println( student1 );
        System.out.println( student2 );
        System.out.println( "\n" ); // 修改student1的值類型字段 student1.setAge( 35 ); // 修改student1的引用類型字段 m.setMajorName( "電子信息工程" );
        m.setMajorId( 888888 );

        System.out.println( student1 );
        System.out.println( student2 );

    }
}

運行得到如下結(jié)果:

從結(jié)果可以看出:

  • student1==student2打印false,說明clone()方法的確克隆出了一個新對象;
  • 修改值類型字段并不影響克隆出來的新對象,符合預期;
  • 而修改了student1內(nèi)部的引用對象,克隆對象student2也受到了波及,說明內(nèi)部還是關(guān)聯(lián)在一起的

深拷貝代碼實現(xiàn)

深度遍歷式拷貝

雖然clone()方法可以完成對象的拷貝工作,但是注意:clone()方法默認是淺拷貝行為,就像上面的例子一樣。若想實現(xiàn)深拷貝需覆寫clone()方法實現(xiàn)引用對象的深度遍歷式拷貝,進行地毯式搜索。

所以對于上面的例子,如果想實現(xiàn)深拷貝,首先需要對更深一層次的引用類Major做改造,讓其也實現(xiàn)Cloneable接口并重寫clone()方法:

public class Major implements Cloneable { @Override protected Object clone() throws CloneNotSupportedException { return super.clone();
    } // ... 其他省略 ... }

其次我們還需要在頂層的調(diào)用類中重寫clone方法,來調(diào)用引用類型字段的clone()方法實現(xiàn)深度拷貝,對應到本文那就是Student類:

public class Student implements Cloneable { @Override public Object clone() throws CloneNotSupportedException {
        Student student = (Student) super.clone();
        student.major = (Major) major.clone(); // 重要?。。?/span> return student;
    } // ... 其他省略 ... }

這時候上面的測試用例不變,運行可得結(jié)果:

很明顯,這時候student1和student2兩個對象就完全獨立了,不受互相的干擾。

利用反序列化實現(xiàn)深拷貝

記得在前文《序列化/反序列化,我忍你很久了》中就已經(jīng)詳細梳理和總結(jié)了「序列化和反序列化」這個知識點了。

利用反序列化技術(shù),我們也可以從一個對象深拷貝出另一個復制對象,而且這貨在解決多層套娃式的深拷貝問題時效果出奇的好。

所以我們這里改造一下Student類,讓其clone()方法通過序列化和反序列化的方式來生成一個原對象的深拷貝副本:

public class Student implements Serializable { private String name; // 姓名 private int age; // 年齡 private Major major; // 所學專業(yè) public Student clone() { try { // 將對象本身序列化到字節(jié)流 ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
            ObjectOutputStream objectOutputStream = new ObjectOutputStream( byteArrayOutputStream );
            objectOutputStream.writeObject( this ); // 再將字節(jié)流通過反序列化方式得到對象副本 ObjectInputStream objectInputStream = new ObjectInputStream( new ByteArrayInputStream( byteArrayOutputStream.toByteArray() ) ); return (Student) objectInputStream.readObject();

        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } return null;
    } // ... 其他省略 ... }

當然這種情況下要求被引用的子類(比如這里的Major類)也必須是可以序列化的,即實現(xiàn)了Serializable接口:

public class Major implements Serializable { // ... 其他省略 ... }

這時候測試用例完全不變,直接運行,也可以得到如下結(jié)果:

很明顯,這時候student1和student2兩個對象也是完全獨立的,不受互相的干擾,深拷貝完成。


后 記

好了,關(guān)于「深拷貝」和「淺拷貝」這個問題這次就聊到這里吧。本以為這篇會很快寫完,結(jié)果又扯出了這么多東西,不過這樣一梳理、一串聯(lián),感覺還是清晰了不少。


—————END—————


免責聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關(guān)機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
關(guān)閉
關(guān)閉