Java中對于深拷貝和淺拷貝的理解

基本類型和引用類型在內存中的保存

Java中數(shù)據(jù)類型分為兩大類:基本類型和對象類型
變量也有兩種類型:基本類型和引用類型

基本類型的變量保存原始值遭商,即它代表的值就是數(shù)值本身
引用類型的變量保存引用值整以,"引用值"指向內存空間的地址盘榨,代表了某個對象的引用,而不是對象本身,對象本身存放在這個引用值所表示的地址的位置。
基本類型包括:byte,short,int,long,char,float,double,boolean
引用類型包括:類類型辽幌,接口類型和數(shù)組

引用傳遞和值傳遞

值傳遞:

方法調用時,實際參數(shù)把它的值傳遞給對應的形式參數(shù)椿访,函數(shù)接收的是原始值的一個copy乌企,此時內存中存在兩個相等的基本類型,即實際參數(shù)和形式參數(shù)成玫,后面方法中的操作都是對形參這個值的修改加酵,不影響實際參數(shù)的值。

引用傳遞:

也稱為傳地址哭当。方法調用時猪腕,實際參數(shù)的引用(地址,而不是參數(shù)的值)被傳遞給方法中相對應的形式參數(shù)钦勘,函數(shù)接收的是原始值的內存地址陋葡;
在方法執(zhí)行中,形參和實參內容相同彻采,指向同一塊內存地址脖岛,方法執(zhí)行中對引用的操作將會影響到實際對象朵栖。

直接看下例子

public class Test {
    public static void main(String args[]) {
        int number=123;
        Student stu1 = new Student();
        stu1.setNumber(number);
        Student stu2 =stu1;
        stu2.setNumber(321);
        System.out.println(number);
        System.out.println(stu1 == stu2);
        System.out.println("學生1:" + stu1.getNumber());
        System.out.println("學生2:" + stu2.getNumber());
    }
    static class Student{
        private int number;
        public int getNumber() {
            return number;
        }
        public void setNumber(int number) {
            this.number = number;
        } 
    }
}

測試結果:

image.png

main方法中:
number是基本類型,采用值傳遞柴梆,不影響實際參數(shù)的值。
Student是類類型终惑,采用引用傳遞绍在,指向同一塊內存地址,對引用的操作將會影響到實際對象雹有。

Student stu2 =stu1;引用傳遞把stu1stu2指向內存堆中同一個對象偿渡。所以當stu2.setNumber(321);時,stu1中的number值也會發(fā)生改變

淺拷貝

想要復制一個新對象霸奕,也就是開辟一塊新的內存地址溜宽,那么就要使用到拷貝
1.需要實現(xiàn)Clonenable接口,該接口為標記接口(不含任何方法)
2.覆蓋clone()方法质帅,訪問修飾符設為public适揉。方法中調用super.clone()方法得到需要的復制對象

public class Test {
    public static void main(String args[]) {
        int number=123;
        Student stu1 = new Student();
        stu1.setNumber(number);
        Student stu2 = (Student)stu1.clone();
        stu2.setNumber(321);
        System.out.println(number);
        System.out.println(stu1 == stu2);
        System.out.println("學生1:" + stu1.getNumber());
        System.out.println("學生2:" + stu2.getNumber());
    }
    static class Student implements Cloneable{
        private int number;
        public int getNumber() {
            return number;
        }
        public void setNumber(int number) {
            this.number = number;
        } 
        @Override
        public Object clone() {
            Student stu = null;
            try{
                stu = (Student)super.clone();
            }catch(CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return stu;
        }
    }
}

測試結果:

image.png

main方法中用Student stu2 = (Student)stu1.clone();代替Student stu2 =stu1;,這樣才會指向不同的內存地址煤惩,stu1 == stu2false說明指向的地址塊是不同的嫉嘀,所以對象中number值也是不同的

深拷貝

之前在Student對象中的成員屬性都是基本類型的,那如果是引用類型的呢魄揉?
先看測試代碼

public class Test {
    public static void main(String args[]) {
        int number=123;
        Address addr = new Address();
        addr.setAdd("A");
        Student stu1 = new Student();
        stu1.setNumber(number);
        stu1.setAddr(addr);
        Student stu2 = (Student)stu1.clone();
        stu2.setNumber(321);
        addr.setAdd("B");
        System.out.println(number);
        System.out.println(stu1 == stu2);
        System.out.println(stu1.getAddr()==stu2.getAddr());
        System.out.println("學生1:" + stu1.getNumber() + ",地址:" + stu1.getAddr().getAdd());
        System.out.println("學生2:" + stu2.getNumber() + ",地址:" + stu2.getAddr().getAdd());
    }
    static class Student implements Cloneable{
        private int number;
        private Address addr;
        public Address getAddr() {
            return addr;
        }
        public void setAddr(Address addr) {
            this.addr = addr;
        }
        public int getNumber() {
            return number;
        }
        public void setNumber(int number) {
            this.number = number;
        }
        @Override
        public Object clone() {
            Student stu = null;
            try{
                stu = (Student)super.clone();
            }catch(CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return stu;
        }
    }
    static class Address {
        private String add;
        public String getAdd() {
            return add;
        }
        public void setAdd(String add) {
            this.add = add;
        }
    }
}

測試結果:

image.png

可以看出Student對象指向不同的內存地址剪侮,但是成員屬性Address對象在stu1stu2中都是指向同一內存地址

原因是淺拷貝只是拷貝了addr變量的引用,并沒有真正的開辟另一塊空間洛退,將值復制后再將引用返回給新對象瓣俯。
所以,為了達到真正的拷貝對象兵怯,而不是純粹引用復制彩匕。我們需要將Address類可復制化,并且修改clone方法摇零,同時Student類也要做下修改

static class Student implements Cloneable{
        private int number;
        private Address addr;
        public Address getAddr() {
            return addr;
        }
        public void setAddr(Address addr) {
            this.addr = addr;
        }
        public int getNumber() {
            return number;
        }
        public void setNumber(int number) {
            this.number = number;
        }
        @Override
        public Object clone() {
            Student stu = null;
            try{
                stu = (Student)super.clone();
            }catch(CloneNotSupportedException e) {
                e.printStackTrace();
            }
            stu.addr = (Address)addr.clone();
            return stu;
        }
    }
    static class Address  implements Cloneable{
        private String add;
        public String getAdd() {
            return add;
        }
        public void setAdd(String add) {
            this.add = add;
        }
        @Override
        public Object clone() {
            Address addr = null;
            try{
                addr = (Address)super.clone();
            }catch(CloneNotSupportedException e) {
                e.printStackTrace();
            }
            return addr;
        }
    }

重點:
1.在Address類中實現(xiàn)Cloneable接口推掸,修改clone方法
2.在Student類的clone方法中加上stu.addr = (Address)addr.clone();實現(xiàn)深拷貝


關于clone()

JDK API的說明文檔解釋這個方法將返回Object對象的一個拷貝。

new的區(qū)別

共同點:都是分配內存驻仅,對象都是指向不同的內存地址
不同點:new創(chuàng)建一個對象谅畅,clone復制一個對象。new是返回的新對象噪服,而調用clone()方法時毡泻,拷貝對象已經(jīng)包含了一些原來對象的信息,而不是對象的初始信息

復制引用(引用傳遞)和復制對象

引用都存放在棧區(qū)粘优,類對象存放于堆區(qū)

image.png

從源碼角度看clone()
protected native Object clone() throws CloneNotSupportedException;

Object類的clone()是一個native方法仇味,native方法的效率一般來說都是遠高于Java中的非native方法呻顽。這也解釋了為 什么要用Objectclone()方法而不是先new一個類,然后把原始對象中的信息賦到新對象中丹墨,雖然這也實現(xiàn)了clone功能廊遍。Java中所有的類是缺省繼承Object類的,所以可以直接使用protected屬性的方法

為什么要實現(xiàn)Cloneable接口

Cloneable接口是不包含任何方法的贩挣,其實這個接口僅僅是一個標志喉前,而且這個標志也僅僅是針對 Object類中clone()方法的,如果實現(xiàn)clone()方法的類沒有實現(xiàn)Cloneable接口王财,并調用了Objectclone()方法(也就是調用了 super.clone()方法)卵迂,那么Objectclone()方法就會拋出CloneNotSupportedException異常

內存地址

在以上測試中我都是以==去判斷兩個對象是否指向同一內存地址,如果要查看具體的內存地址值可以這樣做
1.可以使用hashCode()方法绒净,在不重寫hashCode()的情況下见咒,默認是返回內存地址,不同的hash值代表不同的內存地址
2.使用System.identityHashCode(Object)方法可以返回對象的內存地址,不管該對象的類是否重寫了hashCode()方法

源碼展示

Object類下
public native int hashCode();
System類下
public static native int identityHashCode(Object x);
拓展

String類重寫了hashcode()

private int hash; 
public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

Java中所有類是缺省繼承Object類的挂疆,所以都有資格去重寫hashcode()方法

總結:

對象內部只有基本數(shù)據(jù)類型改览,那用 clone() 方法獲取到的就是這個對象的深拷貝
而如果對象內部還有引用數(shù)據(jù)類型囱嫩,那用 clone() 方法就是一次淺拷貝操作恃疯。

淺拷貝

對基本數(shù)據(jù)類型進行值傳遞,對引用數(shù)據(jù)類型進行引用傳遞般的拷貝墨闲,此為淺拷貝

深拷貝

對基本數(shù)據(jù)類型進行值傳遞今妄,對引用數(shù)據(jù)類型,會對引用指向的對象進行拷貝鸳碧,此為深拷貝盾鳞。也就是在clone()方法對其內的引用類型的變量再進行一次 clone()

區(qū)別就在于是否對對象中的引用變量所指向的對象進行拷貝。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末瞻离,一起剝皮案震驚了整個濱河市腾仅,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌套利,老刑警劉巖推励,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異肉迫,居然都是意外死亡验辞,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進店門喊衫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來跌造,“玉大人,你說我怎么就攤上這事族购】翘埃” “怎么了陵珍?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長违施。 經(jīng)常有香客問我互纯,道長,這世上最難降的妖魔是什么醉拓? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任伟姐,我火速辦了婚禮,結果婚禮上亿卤,老公的妹妹穿的比我還像新娘。我一直安慰自己鹿霸,他們只是感情好排吴,可當我...
    茶點故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著懦鼠,像睡著了一般钻哩。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上肛冶,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天街氢,我揣著相機與錄音,去河邊找鬼睦袖。 笑死珊肃,一個胖子當著我的面吹牛,可吹牛的內容都是我干的馅笙。 我是一名探鬼主播伦乔,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼董习!你這毒婦竟也來了烈和?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤皿淋,失蹤者是張志新(化名)和其女友劉穎招刹,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體窝趣,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡疯暑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了高帖。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缰儿。...
    茶點故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖散址,靈堂內的尸體忽然破棺而出乖阵,到底是詐尸還是另有隱情宣赔,我是刑警寧澤,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布瞪浸,位于F島的核電站儒将,受9級特大地震影響,放射性物質發(fā)生泄漏对蒲。R本人自食惡果不足惜钩蚊,卻給世界環(huán)境...
    茶點故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蹈矮。 院中可真熱鬧砰逻,春花似錦、人聲如沸泛鸟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽北滥。三九已至刚操,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間再芋,已是汗流浹背菊霜。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留济赎,地道東北人鉴逞。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像联喘,于是被迫代替她去往敵國和親华蜒。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,543評論 2 349

推薦閱讀更多精彩內容

  • 1.ios高性能編程 (1).內層 最小的內層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結構(3).初始化時...
    歐辰_OSR閱讀 29,339評論 8 265
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,092評論 1 32
  • 1. Java基礎部分 基礎部分的順序:基本語法豁遭,類相關的語法叭喜,內部類的語法,繼承相關的語法蓖谢,異常的語法捂蕴,線程的語...
    子非魚_t_閱讀 31,598評論 18 399
  • 今天兩位舍友發(fā)生了一點小矛盾,于是闪幽,我們的宿舍頓時烏云密布啥辨。 在這個時候,交流的作用就凸顯出來了盯腌,在任何時候溉知,我們...
    小犟閱讀 169評論 0 0
  • 害羞草 在大自然中,有一種奇妙的植物——含羞草。 含羞草的葉子是一片一片的级乍,葉子的形狀是橢圓形的舌劳,他的葉子不像爬山...
    翱翔在那閱讀 219評論 0 0