CV程序員都要掌握的對象拷貝

前言

CV程序員:熟練使用ctrl+c和ctrl+v或者command+c和command+v的程序員,CV程序員分兩種,第一種見多識廣,遊走在各類搜索引擎、博客以及項目中,搜尋着本身想要的代碼,第一種是把本身的代碼從一個類複製到另外一個類。java

我以爲若是是做爲一個剛起步的程序員來講,CV別人的代碼挺好的,由於至少他具有了搜索以及本身解決問題的能力。這種能力和學習方式是很是重要的,由於不少問題都須要本身去解決一遍纔會慢慢積累經驗,向更高的水平進階,可是要注意的是懂得回顧和反思,雖然已經把代碼CV過來了,可是過後須要思考代碼爲何要這麼寫,而不是解決了就丟一旁。日積跬步,終有一天你的代碼也會被別人用來CV。而若是是CV本身的代碼就要注意了,由於你在寫着冗餘的代碼,須要思考爲何有這麼多重複的代碼,是否能夠作一些抽象設計或者運用一些設計模式來避免代碼的冗餘。程序員

今天要講的是CV程序員都要掌握的對象拷貝,主要是從如下三個方面開始講解:設計模式

克隆什麼?markdown

怎麼克隆?ide

爲何要克隆?學習

深拷貝和淺拷貝區別是什麼?測試

克隆什麼?

要講克隆,先弄清楚第一個問題:克隆什麼東西?這還用回答嗎,固然是克隆對象。可是到底克隆的是什麼類型的對象?this

咱們知道在 Java中基本數據類型和引用數據類型之分,基本數據類型與引用數據類型最本質的區別是:搜索引擎

  • 基本數據類型直接包含值,在被建立時,在棧上給其劃分一塊內存,將數值直接存儲在棧上。
  • 引用數據類型被建立時,首先要在棧上給其引用(句柄)分配一塊內存,而對象的具體信息都存儲在堆內存上,而後由棧上面的引用指向堆中對象的地址,能夠理解爲棧上面保存的是堆中對象的地址。

而咱們所說的克隆,能夠分這兩種類型的對象來討論。spa

偷偷引入一個知識點:

裝箱:將基本數據類型轉換爲引用數據類型。好比int轉化爲Integer。

拆箱:將引用數據類型轉換爲基本數據類型。好比Integer轉化爲int。

怎麼克隆?

弄清楚了要克隆什麼,那麼咱們來看看怎麼克隆。仍是分爲克隆基本類型對象和引用類型對象:

基本類型對象克隆

下面是一個基本類型的克隆示例:

public static void main(String[] args) {
    int a = 1;
    // 用"="克隆
    int b = a;
    System.out.println("a=" + a);
    System.out.println("b=" + b);
    // 測試是否修改了a的值
    b = 2;
    System.out.println("修改了b的值後,a=" + a);
    System.out.println("修改了b的值後,b=" + b);

}
複製代碼

運行結果

對於基本類型來講,直接用「=」就能完成克隆,而且這樣克隆出來的對象是徹底獨立於原來的對象的。

引用類型對象

引用類型對象的克隆有兩種方法

  • 用」=「進行克隆
  • 用Object類的clone方法進行克隆

下面先定義一個類:

public class School implements Cloneable {
    /** * 校名 */
    String name;
    /** * 校長 */
    String president;

    public School(String name, String president) {
        this.name = name;
        this.president = president;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setPresident(String president) {
        this.president = president;
    }

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

    @Override
    public String toString() {
        return "School:[name=" + name + "," +
                "president=" + president + "]";
    }
}
複製代碼

用」=「進行克隆

示例

public static void main(String[] args) throws CloneNotSupportedException {
    School school = new School("家裏蹲", "crazyhzm");
    // 用"="克隆
    School schoolDuplicate = school;
    System.out.println("school的hashcode=" + school.hashCode());
    System.out.println("schoolDuplicate的hashcode=" + schoolDuplicate.hashCode());
    System.out.println("school的值=" + school.toString());
    System.out.println("schoolDuplicate的值=" + schoolDuplicate.toString());
    // 修改 schoolDuplicate的值
    schoolDuplicate.setPresident("加點代碼調調味");
    System.out.println("修改schoolDuplicate的值後,school的值=" + school.toString());
    System.out.println("修改schoolDuplicate的值後,schoolDuplicate的值=" + schoolDuplicate.toString());
}
複製代碼

運行結果

從運行結果看,用「=」進行克隆,其實只是克隆了原對象的內存地址,schoolDuplicate和school對象都指向了同一塊內存區域,

用Object類的Clone方法進行克隆

示例

public static void main(String[] args) throws CloneNotSupportedException {
    School school = new School("家裏蹲", "crazyhzm");
    // 用"clone"克隆
    School schoolDuplicateForClone = (School) school.clone();
    System.out.println("school的hashcode=" + school.hashCode());
    System.out.println("schoolDuplicateForClone的hashcode=" + schoolDuplicateForClone.hashCode());
    System.out.println("school的值=" + school.toString());
    System.out.println("schoolDuplicateForClone的值=" + schoolDuplicateForClone.toString());

    // 修改 schoolDuplicateForClone的值
    schoolDuplicateForClone.setPresident("點個贊");
    System.out.println("修改schoolDuplicateForClone的值後,school的值=" + school.toString());
    System.out.println("修改schoolDuplicateForClone的值後,schoolDuplicateForClone的值=" + schoolDuplicateForClone.toString());

}
複製代碼

運行結果

爲何要克隆?

瞭解了克隆什麼以及怎麼克隆後,咱們到底爲何要使用克隆?

從JDK1.0版本開始,Java語言就提供了克隆機制。要知道咱們爲何要克隆,先來了解一下克隆這種行爲有什麼方法替代,對於CV程序員來講,若是不能 複製黏貼,那就知道本身把代碼一行行本身敲上去。那麼對於對象的克隆來講,無非就是本身new一個新的對象,而後將其中的屬性一個一個賦值過去,若是遇到引用數據類型,還須要繼續new,繼續賦值。當想要建立的新對象的屬性值與原來的對象的屬性值保持一致時,這種手動new,再一個一個賦值的行爲就看似有點繁瑣了,這種時候咱們就能夠用到克隆。從上面怎麼克隆能夠看到會出現兩種不同的結果,在克隆中,須要搞清楚什麼是深拷貝和淺拷貝。

深拷貝和淺拷貝區別是什麼?

在這裏姑且把克隆改成拷貝,它存在深拷貝和淺拷貝,咱們知道一個對象的屬性中可能存在着另外一個對象的引用,而這個對象是引用數據類型的。而淺克隆後的對象中非基本對象和原對象指向同一塊內存,所以對這些非基本對象的修改會同時更改克隆先後的對象。深克隆能夠實現徹底的克隆,能夠用反射的方式或序列化的方式實現。具體是怎麼樣的,我修改一下上述的示例來介紹:

淺拷貝

下面仍是School類的定義,可是新增了一個Grade屬性。

public class School implements Cloneable {
    /** * 校名 */
    String name;
    /** * 校長 */
    String president;

    Grade grade;

    public School(String name, String president) {
        this.name = name;
        this.president = president;
    }

    public School(String name, String president, Grade grade) {
        this.name = name;
        this.president = president;
        this.grade = grade;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setPresident(String president) {
        this.president = president;
    }

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

    @Override
    public String toString() {
        return "School{" +
                "name='" + name + '\'' +
                ", president='" + president + '\'' +
                ", grade=" + grade.toString() +
                '}';
    }
}
複製代碼

Grade類定義:

public class Grade {
    /** * 年級名稱 */
    String name;

    public Grade(String name) {
        this.name = name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Grade{" +
                "name='" + name + '\'' +
                '}';
    }
}
複製代碼

示例代碼

public static void main(String[] args) throws CloneNotSupportedException {
    Grade grade = new Grade("一年級");
    School school = new School("家裏蹲", "crazyhzm", grade);
    // 用"clone"克隆
    School schoolDuplicateForClone = (School) school.clone();
    System.out.println("school的hashcode=" + school.hashCode());
    System.out.println("school.grade的hashcode=" + school.grade.hashCode());
    System.out.println("schoolDuplicateForClone的hashcode=" + schoolDuplicateForClone.hashCode());
    System.out.println("schoolDuplicateForClone.grade的hashcode=" + schoolDuplicateForClone.grade.hashCode());
    System.out.println("school的值=" + school.toString());
    System.out.println("schoolDuplicateForClone的值=" + schoolDuplicateForClone.toString());

    // 修改 schoolDuplicateForClone的值
    schoolDuplicateForClone.setPresident("點個贊");
    schoolDuplicateForClone.grade.setName("二年級");
    System.out.println("修改schoolDuplicateForClone的值後,school的值=" + school.toString());
    System.out.println("修改schoolDuplicateForClone的值後,schoolDuplicateForClone的值=" + schoolDuplicateForClone.toString());

}
複製代碼

運行結果

從運行結果能夠看到,School的拷貝對象修改了年級名稱後,被拷貝對象的值也被修改了,而且能夠從他們的hashcode也能看出來,在拷貝的時候都指向了同一個內存區域,這就是所謂的淺拷貝。

深拷貝

深拷貝咱們稍微修改一下上述的School以及Grade代碼

school類

修改了clone方法。

public class School implements Cloneable {
    /** * 校名 */
    String name;
    /** * 校長 */
    String president;

    Grade grade;

    public School(String name, String president) {
        this.name = name;
        this.president = president;
    }

    public School(String name, String president, Grade grade) {
        this.name = name;
        this.president = president;
        this.grade = grade;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setPresident(String president) {
        this.president = president;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        School school = (School) super.clone();
        school.grade = (Grade) grade.clone();
        return school;
    }

    @Override
    public String toString() {
        return "School{" +
                "name='" + name + '\'' +
                ", president='" + president + '\'' +
                ", grade=" + grade.toString() +
                '}';
    }
}
複製代碼

Grade類

Grade實現了Cloneable接口。

public class Grade implements Cloneable{
    /** * 年級名稱 */
    String name;

    public Grade(String name) {
        this.name = name;
    }

    public void setName(String name) {
        this.name = name;
    }

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

    @Override
    public String toString() {
        return "Grade{" +
                "name='" + name + '\'' +
                '}';
    }
}
複製代碼

仍是執行上述示例代碼,運行後結果爲:

能夠看到school中grade屬性也被拷貝,修改年級名稱後,原對象的數據並無隨之改變,這就是深拷貝。

內推區域

點擊【加點代碼調調味】我的主頁的最新沸點,有內推信息,歡迎諮詢。

送福利區域

掃描下方二維碼關注公衆號【加點代碼調調味】 點擊菜單欄獲取免費49篇的《Dubbo源碼解析》系列文章

相關文章
相關標籤/搜索