最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

淺拷貝和深拷貝原理分析

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

前言

因?yàn)樗婕暗綄?duì)象的引用關(guān)系,涉及到 Java 是傳值還是傳遞引用關(guān)系,這通常是面試的重點(diǎn)。
所以在聊深拷貝和淺拷貝之前,我們先來(lái)聊一聊引用關(guān)系。

關(guān)于引用

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

如下代碼所示

public class Food{
    String name;
    int num;
    String taste;
    constructor()
       get and set()
    toString()
}

測(cè)試類(lèi):

public static void main(String[] args) {
  int i1 = 10;
  int i2 = i1; // 基本數(shù)據(jù)類(lèi)型的拷貝,拷貝值
  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 都指向同一個(gè)堆內(nèi)存對(duì)象
}

如果用圖表示的話,應(yīng)該是下面這樣的:

image-20210807143403402

不用糾結(jié) Java 中到底是值傳遞還是引用傳遞這種無(wú)意義的爭(zhēng)論中

  • 對(duì)于基本數(shù)據(jù)類(lèi)型,傳遞的是數(shù)據(jù)類(lèi)型的值。
  • 對(duì)于引用類(lèi)型來(lái)說(shuō),傳遞的是對(duì)象的引用,也就是對(duì)象的地址就可以了。

關(guān)于淺拷貝和深拷貝

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

淺拷貝

那么如何實(shí)現(xiàn)淺拷貝(Shallow copy)呢?
很簡(jiǎn)單,就是在需要拷貝的類(lèi)上實(shí)現(xiàn) Cloneable 接口并重寫(xiě)其 clone() 方法就可以了。

下面我們對(duì) Food 類(lèi)進(jìn)行修改
我們讓他實(shí)現(xiàn) Cloneable 接口,并重寫(xiě) clone() 方法。

public class Food implements Cloneable{
    ...
  @Override
  protected Object clone() throws CloneNotSupportedException {
    return super.clone();
  }      
  ...
}

然后在測(cè)試類(lèi)中的代碼如下

Food milk = new Food("milk",1,"fragrance");
Food food = (Food)milk.clone();
System.out.println("milk = " + milk);
System.out.println("food = " + food);

可以看到,現(xiàn)在的 food 對(duì)象是由 milk 對(duì)象拷貝出來(lái)的
那么此時(shí)的 food 對(duì)象和 milk 對(duì)象是同一個(gè)對(duì)象嗎?
我們通過(guò)打印,可以看到這兩個(gè)對(duì)象的原生 hashcode。

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

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

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

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

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

雖然看起來(lái)是兩種完全不同的稱(chēng)呼!但是他們卻有一種共同的能力:寫(xiě)作!

我們還是通過(guò)圖示來(lái)說(shuō)明一下:

image-20210817000226163

這幅圖看出門(mén)道了么?在堆區(qū)分別出現(xiàn)了兩個(gè) Food 對(duì)象

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

深拷貝

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

深拷貝原來(lái)就是在淺拷貝的基礎(chǔ)上再?gòu)?fù)制一下它的屬性值,上代碼!

我們先增加一個(gè)飲品類(lèi) Drink

public class Drink implements Cloneable {
    String name;
    get and set()
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
    toString()
}

然后更改一下 Food 類(lèi),因?yàn)?Drink 也算是 Food ,所以我們?cè)?Food 類(lèi)中增加對(duì) Drink 的引用,然后再修改 get set 、toString 、clone 、構(gòu)造方法,修改后的 Food 類(lèi)代碼如下

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 方法,我們?cè)?clone 方法中,實(shí)現(xiàn)了對(duì) Food 對(duì)象的拷貝,同時(shí)也實(shí)現(xiàn)了對(duì) Drink 對(duì)象的拷貝,這就是我們上面所說(shuō)的復(fù)制對(duì)象并復(fù)制對(duì)象的成員變量。

然后我們進(jìn)行一下 Deep Copy的測(cè)試:

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());

}

運(yùn)行完成后的輸出結(jié)果如下:

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

可以看到,我們把 foodClone 拷貝出來(lái)之后,修改 food 中的 drink 變量,卻不會(huì)對(duì) foodClone 造成改變,這就說(shuō)明 foodClone 已經(jīng)成功實(shí)現(xiàn)了深拷貝。

用圖示表示的話,應(yīng)該是下面這樣的:

image-20210807170417799

這是深拷貝之后的內(nèi)存分配圖,現(xiàn)在可以看到,food 和 foodClone 完全是兩個(gè)不同的對(duì)象,它們之間不存在紐帶關(guān)系。

我們上面主要探討實(shí)現(xiàn)對(duì)象拷貝的方式是對(duì)象實(shí)現(xiàn) Cloneable 接口,并且調(diào)用重寫(xiě)之后的 clone 方法,在 Java 中,還有一種實(shí)現(xiàn)對(duì)象拷貝的方式是使用 序列化。

序列化

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

public Person clone() {
  Person person = null;
  try {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ObjectOutputStream oos = new ObjectOutputStream(baos);
    oos.writeObject(this);
    // 將流序列化成對(duì)象
    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;
}

使用序列化可以實(shí)現(xiàn)深拷貝,它的原理是將二進(jìn)制字節(jié)流內(nèi)容寫(xiě)到一個(gè)文本或字節(jié)數(shù)組,然后是從這個(gè)文本或者字節(jié)數(shù)組中讀取數(shù)據(jù),原對(duì)象寫(xiě)入這個(gè)文本或者字節(jié)數(shù)組后再拷貝給 clone 對(duì)象,原對(duì)象的修改不會(huì)影響 clone 對(duì)象,因?yàn)?clone 對(duì)象是從文本或者字節(jié)數(shù)組中讀取的。

如何選擇拷貝方式

到現(xiàn)在我們已經(jīng)把淺拷貝和深拷貝都介紹完了,那么如何選擇淺拷貝和深拷貝呢?下面是幾點(diǎn)注意事項(xiàng)

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

其他拷貝方式

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

比如數(shù)組的拷貝,你可以使用 Arrays.copyof 實(shí)現(xiàn)數(shù)組拷貝,還可以使用默認(rèn)的 clone 進(jìn)行拷貝,不過(guò)這兩者都是淺拷貝。

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

除了基本數(shù)組數(shù)據(jù)類(lèi)型之外的拷貝,還有對(duì)象的拷貝,不過(guò)用法基本是一樣的。

集合也可以實(shí)現(xiàn)拷貝,因?yàn)榧系牡讓泳褪褂玫氖菙?shù)組,所以用法也是一樣的。

一些說(shuō)明

針對(duì) Cloneable 接口,有下面三點(diǎn)使用說(shuō)明

  • 如果類(lèi)實(shí)現(xiàn)了 Cloneable 接口,再調(diào)用 Object 的 clone() 方法可以合法地對(duì)該類(lèi)實(shí)例進(jìn)行按字段復(fù)制。
  • 如果在沒(méi)有實(shí)現(xiàn) Cloneable 接口的實(shí)例上調(diào)用 Object 的 clone() 方法,則會(huì)導(dǎo)致拋出CloneNotSupporteddException。
  • 實(shí)現(xiàn)此接口的類(lèi)應(yīng)該使用公共方法重寫(xiě) Object 的clone() 方法,因?yàn)?Object 的 clone() 方法是一個(gè)受保護(hù)的方法。

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

相關(guān)文章

  • java中Map集合的常用方法總結(jié)大全

    java中Map集合的常用方法總結(jié)大全

    開(kāi)發(fā)中最常用的就是List集合和Map集合,Map集合是基于java核心類(lèi)java.util中的,下面這篇文章主要給大家總結(jié)介紹了關(guān)于java中Map集合的一些常用方法,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2024-01-01
  • Java中使用數(shù)組實(shí)現(xiàn)棧數(shù)據(jù)結(jié)構(gòu)實(shí)例

    Java中使用數(shù)組實(shí)現(xiàn)棧數(shù)據(jù)結(jié)構(gòu)實(shí)例

    這篇文章主要介紹了Java中使用數(shù)組實(shí)現(xiàn)棧數(shù)據(jù)結(jié)構(gòu)實(shí)例,本文先是講解了實(shí)現(xiàn)棧至少應(yīng)該包括以下幾個(gè)方法等知識(shí),然后給出代碼實(shí)例,需要的朋友可以參考下
    2015-01-01
  • SpringMVC?RESTFul實(shí)戰(zhàn)案例修改功能實(shí)現(xiàn)

    SpringMVC?RESTFul實(shí)戰(zhàn)案例修改功能實(shí)現(xiàn)

    這篇文章主要為大家介紹了SpringMVC?RESTFul實(shí)戰(zhàn)案例修改功能實(shí)現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-05-05
  • Java中的FileInputStream是否需要close問(wèn)題

    Java中的FileInputStream是否需要close問(wèn)題

    這篇文章主要介紹了Java中的FileInputStream是否需要close問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-12-12
  • Java中多態(tài)的實(shí)現(xiàn)原理詳細(xì)解析

    Java中多態(tài)的實(shí)現(xiàn)原理詳細(xì)解析

    這篇文章主要介紹了Java中多態(tài)的實(shí)現(xiàn)原理詳細(xì)解析,多態(tài)是面向?qū)ο缶幊陶Z(yǔ)言的重要特性,它允許基類(lèi)的指針或引用指向派生類(lèi)的對(duì)象,而在具體訪問(wèn)時(shí)實(shí)現(xiàn)方法的動(dòng)態(tài)綁定,需要的朋友可以參考下
    2024-01-01
  • SpringCloud Gateway 權(quán)限認(rèn)證的實(shí)現(xiàn)

    SpringCloud Gateway 權(quán)限認(rèn)證的實(shí)現(xiàn)

    Spring Cloud Gateway 作為網(wǎng)關(guān)層,承擔(dān)著請(qǐng)求轉(zhuǎn)發(fā)、權(quán)限校驗(yàn)等重要職責(zé),本文主要介紹了SpringCloud Gateway 權(quán)限認(rèn)證的實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下
    2025-04-04
  • java枚舉的使用示例

    java枚舉的使用示例

    我們?cè)趯W(xué)習(xí)編程語(yǔ)言的時(shí)候都學(xué)過(guò)枚舉,現(xiàn)在就具體來(lái)看看java中的枚舉的使用
    2013-12-12
  • SpringBoot單機(jī)限流的實(shí)現(xiàn)

    SpringBoot單機(jī)限流的實(shí)現(xiàn)

    在系統(tǒng)運(yùn)維中, 有時(shí)候?yàn)榱吮苊庥脩舻膼阂馑⒔涌? 會(huì)加入一定規(guī)則的限流,本文主要介紹了SpringBoot單機(jī)限流的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-08-08
  • java??常見(jiàn)位邏輯運(yùn)算符梳理

    java??常見(jiàn)位邏輯運(yùn)算符梳理

    這篇文章主要介紹了java常見(jiàn)位邏輯運(yùn)算符梳理,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,感興趣的小伙伴可以參一下下面文章詳細(xì)內(nèi)容
    2022-08-08
  • SpringMVC異常處理器編寫(xiě)及配置

    SpringMVC異常處理器編寫(xiě)及配置

    這篇文章主要介紹了SpringMVC異常處理器編寫(xiě)及配置,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-08-08

最新評(píng)論

慈溪市| 沙雅县| 拉萨市| 宣武区| 海宁市| 西乌珠穆沁旗| 萝北县| 大田县| 齐齐哈尔市| 黑河市| 化德县| 滦平县| 南投市| 丹阳市| 磐石市| 万全县| 满城县| 金阳县| 淄博市| 固镇县| 乌恰县| 都江堰市| 稻城县| 开化县| 天气| 阿瓦提县| 子长县| 兴业县| 陆丰市| 青岛市| 扎鲁特旗| 筠连县| 乌兰察布市| 吴桥县| 新宁县| 剑川县| 宣城市| 丰台区| 通化市| 桓台县| 连州市|