不想寫對(duì)象沈撞,就克隆一個(gè)吧

1.為什么需要克驴独蟆?

比如有時(shí)我們要獲得一個(gè)非常復(fù)雜的對(duì)象缠俺,與其使用new創(chuàng)建對(duì)象再對(duì)各個(gè)屬性賦值显晶,不如直接克隆現(xiàn)有的對(duì)象。在java中對(duì)于基本類型可以使用"="進(jìn)行克隆壹士,而對(duì)于引用類型卻不能這樣做磷雇。

為什么不能對(duì)引用類型直接使用“=”呢?

答:因?yàn)閖ava將內(nèi)存空間分為2塊墓卦,即堆和棧倦春。在棧中保存基本類型和引用類型的變量,在堆中保存對(duì)象落剪。如果使用“=”,將修改引用睁本。此時(shí)兩個(gè)引用變量指向同一個(gè)對(duì)象,如果其中的一個(gè)引用變量進(jìn)行修改也會(huì)改變另一個(gè)變量忠怖。

舉例說明:
光頭強(qiáng)-->銀行卡<--灰太狼

光頭強(qiáng)和灰太狼共同擁有一張銀行卡呢堰,無論誰向里面存錢,都會(huì)對(duì)對(duì)方產(chǎn)生影響凡泣。

2.克隆的錯(cuò)誤方式(假克峦魈邸)

package test;

public class Test {

    public static void main(String[] args) {
        
        Person p = new Person("懶洋洋",10);
        System.out.println("克隆前的Person");
        System.out.println(p.toString());
        Person pClone = p;
        //對(duì)name和age重新賦值
        pClone.name = "光頭強(qiáng)";
        pClone.age = 80;
        System.out.println("克隆后的Person");
        System.out.println(pClone.toString());
        System.out.println("pClone修改屬性后,輸出p的信息");
        System.out.println(p.toString());
        
    }

}

測(cè)試結(jié)果:

克隆前的Person
姓名:懶洋洋 年齡:10
克隆后的Person
姓名:光頭強(qiáng) 年齡:80
pClone修改屬性后鞋拟,輸出p的信息
姓名:光頭強(qiáng) 年齡:80

其他知識(shí)點(diǎn):棧中的知識(shí)點(diǎn)使用完后會(huì)立即被回收骂维,而堆中的對(duì)象是由java虛擬機(jī)管理的,即使該對(duì)象已經(jīng)不再使用贺纲,該內(nèi)存空間只會(huì)在一個(gè)不確定的時(shí)間被回收

3.Java對(duì)象的淺克隆

  1. 為什么叫淺克潞焦搿?

    答:因?yàn)閷?duì)基本類型的域(各個(gè)屬性或方法)的復(fù)制是行之有效的,而對(duì)于引用類型的域可能會(huì)出現(xiàn)錯(cuò)誤潦刃。但是它避免了“一個(gè)引用變量進(jìn)行修改影響另一個(gè)”的問題侮措。

  2. 需要實(shí)現(xiàn)的接口

    答:需要實(shí)現(xiàn)Cloneable接口,如果不實(shí)現(xiàn)這個(gè)接口乖杠,clone()方法會(huì)拋出CloneNotSupportedException異常分扎。

代碼測(cè)試:

 package test;
/**
 * 創(chuàng)建一個(gè)狗類
 */
public class Dog {
    
    public String dogName;
    public String dogBreed;//狗的品種
    
    public Dog(String dogName, String dogType) {
        this.dogName = dogName;
        this.dogBreed = dogType;
    }
    
    public String toString(){
        StringBuffer sb = new StringBuffer();
        sb.append("狗的名字:"+dogName+",")
          .append("狗的品種:"+dogBreed);
        return sb.toString();
    }
}

再創(chuàng)建一個(gè)人類:

package test;

/**
 * 創(chuàng)建一個(gè)人類
 * @author hgx
 *
 */
public class Person implements Cloneable{
    
    public String name;
    public int age;
    Dog dog = null;
    
    public Person(){

    }
    
    public Person(String name, int age, Dog dog) {
        this.name = name;
        this.age = age;
        this.dog = dog;
    }
    
    //實(shí)現(xiàn)Cloneable接口,重寫clone()方法
    public Person clone(){
        Person p = null;
        try {
            p = (Person) super.clone();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return p;
    }
    
    //每個(gè)類都有toString()方法胧洒,我們可以對(duì)其進(jìn)行重寫
    public String toString(){
        StringBuffer sb = new StringBuffer();
        sb.append("人的名字:"+name+",")
          .append("人的年齡:"+age+",")
          .append("所擁有的狗:"+dog);
        return sb.toString();
    }       
}

測(cè)試類:

 package test;

public class Test {

    public static void main(String[] args) {
        Dog dog = new Dog("希密士", "哈士奇");
        Person p = new Person("懶洋洋", 10, dog);
        System.out.println("克隆前的Person");
        System.out.println(p.toString());
        Person pClone = p.clone();
        // 對(duì)name和age重新賦值
        pClone.name = "光頭強(qiáng)";
        //對(duì)dog進(jìn)行修改
        pClone.dog.dogName = "藏獒";
        System.out.println("克隆后的Person");
        System.out.println(pClone.toString());
        System.out.println("pClone修改屬性后畏吓,輸出p的信息");
        System.out.println(p.toString());

    }

}

測(cè)試結(jié)果:

克隆前的Person
人的名字:懶洋洋,人的年齡:10,所擁有的狗:狗的名字:希密士,狗的品種:哈士奇
克隆后的Person
人的名字:光頭強(qiáng),人的年齡:10,所擁有的狗:狗的名字:藏獒,狗的品種:哈士奇
pClone修改屬性后,輸出p的信息
人的名字:懶洋洋,人的年齡:10,所擁有的狗:狗的名字:藏獒,狗的品種:哈士奇

總結(jié):我們通過結(jié)果可以發(fā)現(xiàn)略荡,克隆后的pClone雖然修改了name和age屬性庵佣,但對(duì)原來p的屬性并沒有影響。但當(dāng)我們對(duì)dog這個(gè)引用進(jìn)行修改后汛兜,原來p的dog也發(fā)生了變化巴粪。這也就說明“淺克隆對(duì)基本類型的復(fù)制小菜一碟,但對(duì)于引用類型的不能完全控制粥谬,除非我們不對(duì)引用類型進(jìn)行修改”
這就是淺克隆存在的問題肛根。但上有政策,下有對(duì)策漏策。遇到深克隆派哲,這些問題都要死掉。

涉及到的其他知識(shí)點(diǎn):StringBuffer掺喻,toString()方法

4.java的深克隆

  1. 為什叫深克掳沤臁?

    這個(gè)問題感耙,在上面我們已經(jīng)了如指掌了褂乍。話不多說,言多必失即硼,看代碼逃片。
    由于大部分代碼相同,所以下面只寫改變的地方

代碼案例:

1.Dog類的改變:實(shí)現(xiàn)Cloneable接口只酥,并重寫了clone()方法

//注意不要忘記實(shí)現(xiàn)Cloneable
@Override
protected Dog clone(){
    Dog dog = null;
    try {
        dog = (Dog) super.clone();
    } catch (CloneNotSupportedException e) {
        e.printStackTrace();
    }
    return dog;
}

2.Person類的改變:在clone()方法里添加了Dog類的克隆

@Override
    public Person clone(){
        Person p = null;
        try {
            p = (Person) super.clone();
            p.dog = dog.clone();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return p;
    }

測(cè)試結(jié)果:

克隆前的Person
人的名字:懶洋洋,人的年齡:10,所擁有的狗:狗的名字:希密士,狗的品種:哈士奇
克隆后的Person
人的名字:光頭強(qiáng),人的年齡:10,所擁有的狗:狗的名字:藏獒,狗的品種:哈士奇
pClone修改屬性后褥实,輸出p的信息
人的名字:懶洋洋,人的年齡:10,所擁有的狗:狗的名字:希密士,狗的品種:哈士奇

總結(jié):結(jié)果已經(jīng)證明我們的目的達(dá)到了,隨意改變一個(gè)克隆都不會(huì)影響到原來的類裂允。

注意:通常情況下损离,克隆對(duì)象時(shí)都需要使用深克隆,但需要注意的是绝编,要是引用類型中還有引用草冈,就都需要實(shí)現(xiàn)Cloneable接口,重寫clone()方法瓮增。

5.深克隆與淺克隆的區(qū)別

  1. 代碼上的不同:淺克隆只是克隆類實(shí)現(xiàn)了Cloneable接口怎棱,而深克隆不僅克隆類實(shí)現(xiàn)了Cloneable接口,而且克隆類里的引用類型也實(shí)現(xiàn)了Cloneable接口绷跑。

  2. 功能上不同拳恋,深克隆解決了淺克隆“不能很好對(duì)引用類型的復(fù)制”的問題。

6.序列化與對(duì)象克隆

1.為什么還要使用序列化進(jìn)行對(duì)象的克隆

如果類有很多類型砸捏,而且引用類型里還有引用類型的話谬运,那我們豈不是每個(gè)都要重寫clone()方法,這樣也太麻煩了垦藏。所以序列化就是來解決這個(gè)難題的梆暖。

2.序列化是什么?

序列化掂骏,簡(jiǎn)單的說就像時(shí)間漏斗一樣轰驳,漏斗上面的沙子不停的落下,直到漏完為止弟灼。而在java中的序列化對(duì)象级解,就是這樣一點(diǎn)一點(diǎn)的將一個(gè)對(duì)象復(fù)制成另一個(gè)對(duì)象。其實(shí)田绑,蠻像3D打印機(jī)的勤哗。

3.大體過程

利用輸出流,將對(duì)象以字節(jié)的形式寫入一個(gè)字節(jié)數(shù)組掩驱,然后再通過輸入流芒划,從這個(gè)字節(jié)數(shù)組中讀出。此時(shí)就不需要考慮引用類型的問題了欧穴。因?yàn)樾蛄谢梢哉f民逼,是從根源入手,從本質(zhì)上實(shí)現(xiàn)克隆苔可。就像水管漏水缴挖,只清除水是沒有用的,只有把水管關(guān)住才行焚辅。

生活感悟:所以問題還是要從根源上解決映屋,才可保證萬無一失。如果java中年遇到一些難題同蜻,我們可以想想在生活中是怎樣解決的棚点。

4.需要實(shí)現(xiàn)的接口

Serializable接口

5.例子代碼

package test;
import java.io.Serializable;
// 創(chuàng)建一個(gè)狗類
public class Dog implements Serializable{
    
    public String dogName;
    public String dogBreed;//狗的品種
    
    public Dog(String dogName, String dogType) {
        this.dogName = dogName;
        this.dogBreed = dogType;
    }
            
    public String toString(){
        StringBuffer sb = new StringBuffer();
        sb.append("狗的名字:"+dogName+",")
          .append("狗的品種:"+dogBreed);
        return sb.toString();
    }   
}

//人類

package test;
/**
 * 創(chuàng)建一個(gè)人類
 */
public class Person implements Cloneable, Serializable {

    public String name;
    public int age;
    Dog dog = null;

    public Person() {

    }

    public Person(String name, int age, Dog dog) {
        this.name = name;
        this.age = age;
        this.dog = dog;
    }

    @Override
    public Person clone() {
        Person p = null;
        ByteArrayOutputStream bao = new ByteArrayOutputStream();
        try {
            ObjectOutputStream oos = new ObjectOutputStream(bao);
            oos.writeObject(this);
            oos.close();
        } catch (IOException e) {

            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        ByteArrayInputStream bis = new ByteArrayInputStream(bao.toByteArray());
        try {
            ObjectInputStream ois = new ObjectInputStream(bis);
            p = (Person) ois.readObject();
            ois.close();
        } catch (IOException | ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return p;
    }

    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append("人的名字:" + name + ",").append("人的年齡:" + age + ",").append("所擁有的狗:" + dog);
        return sb.toString();
    }

}

測(cè)試類不變:
測(cè)試結(jié)果:

克隆前的Person
人的名字:懶洋洋,人的年齡:10,所擁有的狗:狗的名字:希密士,狗的品種:哈士奇
克隆后的Person
人的名字:光頭強(qiáng),人的年齡:10,所擁有的狗:狗的名字:藏獒,狗的品種:哈士奇
pClone修改屬性后,輸出p的信息
人的名字:懶洋洋,人的年齡:10,所擁有的狗:狗的名字:希密士,狗的品種:哈士奇

注意:序列化對(duì)象要求實(shí)現(xiàn)Cloneable和Serializable接口湾蔓,如果類中有引用類型的瘫析,則引用類型也需要實(shí)現(xiàn)Serializable接口。在效率上,序列化要比深克隆慢一點(diǎn)贬循。

7.選擇適當(dāng)?shù)目寺》绞?/h2>

如果類的各個(gè)屬性的基本類型和引用類型不會(huì)變咸包,則可以使用淺克隆。否則使用深克隆杖虾。如果類的結(jié)構(gòu)比較復(fù)雜烂瘫,則使用序列化。

8.transient關(guān)鍵字的應(yīng)用

1.為什么使用transient關(guān)鍵字奇适?

比如有一些屬性坟比,我們不想克隆的新的對(duì)象訪問到它的值(比如密碼),則可以在屬性的前面加上transient關(guān)鍵字嚷往。這樣克隆的時(shí)候葛账,雖然變量克隆到新的對(duì)象中了,但是其值為null(如果為引用類型)或0(如果為基本類型)皮仁。

2.transient注意事項(xiàng)

1. transient只能用來修飾屬性籍琳,不能修飾方法
2. 如果使用序列化一個(gè)類,而這個(gè)類中的引用類型又沒有實(shí)現(xiàn)Serializable接口或者不想對(duì)其序列化魂贬,則需要使用transient修飾該引用類型
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末巩割,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子付燥,更是在濱河造成了極大的恐慌宣谈,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,525評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件键科,死亡現(xiàn)場(chǎng)離奇詭異闻丑,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)勋颖,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門嗦嗡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人饭玲,你說我怎么就攤上這事侥祭。” “怎么了茄厘?”我有些...
    開封第一講書人閱讀 164,862評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵矮冬,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我次哈,道長(zhǎng)胎署,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,728評(píng)論 1 294
  • 正文 為了忘掉前任窑滞,我火速辦了婚禮琼牧,結(jié)果婚禮上恢筝,老公的妹妹穿的比我還像新娘。我一直安慰自己巨坊,他們只是感情好撬槽,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著抱究,像睡著了一般恢氯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上鼓寺,一...
    開封第一講書人閱讀 51,590評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音勋磕,去河邊找鬼妈候。 笑死,一個(gè)胖子當(dāng)著我的面吹牛挂滓,可吹牛的內(nèi)容都是我干的苦银。 我是一名探鬼主播,決...
    沈念sama閱讀 40,330評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼赶站,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼幔虏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起贝椿,我...
    開封第一講書人閱讀 39,244評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤想括,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后烙博,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體瑟蜈,經(jīng)...
    沈念sama閱讀 45,693評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評(píng)論 3 336
  • 正文 我和宋清朗相戀三年渣窜,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了铺根。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,001評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡乔宿,死狀恐怖位迂,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情详瑞,我是刑警寧澤掂林,帶...
    沈念sama閱讀 35,723評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站蛤虐,受9級(jí)特大地震影響党饮,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜驳庭,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評(píng)論 3 330
  • 文/蒙蒙 一刑顺、第九天 我趴在偏房一處隱蔽的房頂上張望氯窍。 院中可真熱鬧,春花似錦蹲堂、人聲如沸狼讨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽政供。三九已至,卻和暖如春朽基,著一層夾襖步出監(jiān)牢的瞬間布隔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工稼虎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留衅檀,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,191評(píng)論 3 370
  • 正文 我出身青樓霎俩,卻偏偏與公主長(zhǎng)得像哀军,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子打却,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容