欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

淺拷貝和深拷貝原理分析

 更新時間:2021年08月20日 17:13:14   作者:程序員cxuan  
Java 對象拷貝是為對象賦值的一種方式,簡單來說就是創(chuàng)建一個和原對象相同的對象,新創(chuàng)建的對象是原對象的一個副本。面試官賊拉喜歡在面試的時候問一問你淺拷貝和深拷貝的原理

前言

因為它涉及到對象的引用關系,涉及到 Java 是傳值還是傳遞引用關系,這通常是面試的重點。
所以在聊深拷貝和淺拷貝之前,我們先來聊一聊引用關系。

關于引用

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

如下代碼所示

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; // 基本數據類型的拷貝,拷貝值
  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 都指向同一個堆內存對象
}

如果用圖表示的話,應該是下面這樣的:

image-20210807143403402

不用糾結 Java 中到底是值傳遞還是引用傳遞這種無意義的爭論中

  • 對于基本數據類型,傳遞的是數據類型的值。
  • 對于引用類型來說,傳遞的是對象的引用,也就是對象的地址就可以了。

關于淺拷貝和深拷貝

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

淺拷貝

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

下面我們對 Food 類進行修改
我們讓他實現 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);

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

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

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

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

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

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

雖然看起來是兩種完全不同的稱呼!但是他們卻有一種共同的能力:寫作!

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

image-20210817000226163

這幅圖看出門道了么?在堆區(qū)分別出現了兩個 Food 對象

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

深拷貝

雖然淺拷貝是一種換湯不換藥的說法,但是在 Java 世界中還是有一種說法是,它就是我們所熟悉的深拷貝(Deep copy),
先來拋出一下深拷貝的定義:在進行對象拷貝的基礎上,對對象的成員變量也依次拷貝的方式被稱為深拷貝。

深拷貝原來就是在淺拷貝的基礎上再復制一下它的屬性值,上代碼!

我們先增加一個飲品類 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 、構造方法,修改后的 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 方法中,實現了對 Food 對象的拷貝,同時也實現了對 Drink 對象的拷貝,這就是我們上面所說的復制對象并復制對象的成員變量。

然后我們進行一下 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());

}

運行完成后的輸出結果如下:

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

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

用圖示表示的話,應該是下面這樣的:

image-20210807170417799

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

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

序列化

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

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

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

如何選擇拷貝方式

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

  • 如果對象的屬性都是基本數據類型,那么可以使用淺拷貝。
  • 如果對象有引用類型,那就要基于具體的需求來選擇淺拷貝還是深拷貝。
  • 如果對象嵌套層數比較多,推薦使用 Serializable 接口實現深拷貝。
  • 如果對象引用任何時候都不會被改變,那么沒必要使用深拷貝,只需要使用淺拷貝就行了。如果對象引用經常改變,那么就要使用深拷貝。沒有一成不變的規(guī)則,一切都取決于具體需求。

其他拷貝方式

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

比如數組的拷貝,你可以使用 Arrays.copyof 實現數組拷貝,還可以使用默認的 clone 進行拷貝,不過這兩者都是淺拷貝。

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

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

集合也可以實現拷貝,因為集合的底層就使用的是數組,所以用法也是一樣的。

一些說明

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

  • 如果類實現了 Cloneable 接口,再調用 Object 的 clone() 方法可以合法地對該類實例進行按字段復制。
  • 如果在沒有實現 Cloneable 接口的實例上調用 Object 的 clone() 方法,則會導致拋出CloneNotSupporteddException。
  • 實現此接口的類應該使用公共方法重寫 Object 的clone() 方法,因為 Object 的 clone() 方法是一個受保護的方法。

到此這篇關于淺拷貝和深拷貝原理分析的文章就介紹到這了,更多相關淺拷貝和深拷貝內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Spring切入點表達式配置過程圖解

    Spring切入點表達式配置過程圖解

    這篇文章主要介紹了Spring切入點表達式配置過程圖解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-08-08
  • Java讓多線程按順序執(zhí)行的幾種方法

    Java讓多線程按順序執(zhí)行的幾種方法

    本文主要介紹了Java讓多線程按順序執(zhí)行的幾種方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2022-05-05
  • Java中逆序遍歷List集合的實現

    Java中逆序遍歷List集合的實現

    本文主要介紹了Java中逆序遍歷List集合的實現,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-01-01
  • 利用ssh實現服務器文件上傳下載

    利用ssh實現服務器文件上傳下載

    這篇文章主要為大家詳細介紹了如何利用ssh實現服務器文件上傳下載,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2016-09-09
  • java中PreparedStatement和Statement詳細講解

    java中PreparedStatement和Statement詳細講解

    這篇文章主要介紹了java中PreparedStatement和Statement詳細講解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2019-11-11
  • 解決微服務中關于用戶token處理到的坑

    解決微服務中關于用戶token處理到的坑

    這篇文章主要介紹了解決微服務中關于用戶token處理到的坑,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • 解決MyEclipse中Maven設置jdk版本jdk1.8報錯問題

    解決MyEclipse中Maven設置jdk版本jdk1.8報錯問題

    今天安裝了jdk1.8、tomcat8、和maven3.5.2,弄好后在myeclipse新建了一個maven項目,項目默認是jdk1.5,改成jdk1.8后項目報錯
    2018-10-10
  • mybatis plus代碼生成器配置過程解析

    mybatis plus代碼生成器配置過程解析

    這篇文章主要介紹了mybatis plus代碼生成器配置過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-11-11
  • Spring 與 JDK 線程池的簡單使用示例詳解

    Spring 與 JDK 線程池的簡單使用示例詳解

    這篇文章主要介紹了Spring 與 JDK 線程池的簡單使用,本文通過示例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-09-09
  • java生成自增編號數字的問題

    java生成自增編號數字的問題

    這篇文章主要介紹了java生成自增編號數字的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-09-09

最新評論