Cloneable接口和Object的clone()方法

爲何要克隆html

爲何要使用克隆,這其實反映的是一個很現實的問題,假如咱們有一個對象:app

public class SimpleObject implements Cloneable
{
    private String str;
    
    public SimpleObject()
    {
        System.out.println("Enter SimpleObject.constructor()");
    }

    public String getStr()
    {
        return str;
    }

    public void setStr(String str)
    {
        this.str = str;
    }
    
    public Object clone() throws CloneNotSupportedException
    {
        return super.clone();
    }
}

如今我寫一段程序:函數

public static void main(String[] args)
{
    SimpleObject so0 = new SimpleObject();
    so0.setStr("111");
    System.out.println("so0.getStr():" + so0.getStr());
    SimpleObject so1 = so0;
    so1.setStr("222");
    System.out.println("so0.getStr():" + so0.getStr());
    System.out.println("so1.getStr():" + so1.getStr());
}

運行結果其實很明顯:this

so0.getStr():111
so0.getStr():222
so1.getStr():222

Java底層使用C/C++實現的,"="這個運算符,若是左右兩邊都是對象引用的話,在Java中表示的將等號右邊的引用賦值給等號左邊的引用,兩者指向的仍是同一塊內存,因此任何一個引用對內存的操做都直接反映到另外一個引用上。spa

可是,如今我想拿這個so0的數據進行一些操做,不想改變原來so0中的內容,這時候就可使用克隆了,它容許在堆中克隆出一塊和原對象同樣的對象,並將這個對象的地址賦予新的引用,這樣,顯然我對新引用的操做,不會影響到原對象。code

固然,理解克隆,最好仍是對Java內存區域有比較好的理解。htm

 

Cloneable接口和Object的clone()方法對象

Java中實現了Cloneable接口的類有不少,像咱們熟悉的ArrayList、Calendar、Date、HashMap、Hashtable、HashSet、LinkedList等等。blog

仍是那句話,對於不熟悉的接口、方法,第一反應必定是查詢JDK API。接口

一、Cloneable接口

三句話總結:

(1)此類實現了Cloneable接口,以指示Object的clone()方法能夠合法地對該類實例進行按字段複製

(2)若是在沒有實現Cloneable接口的實例上調用Object的clone()方法,則會致使拋出CloneNotSupporteddException

(3)按照慣例,實現此接口的類應該使用公共方法重寫Object的clone()方法,Object的clone()方法是一個受保護的方法

二、Object的clone()方法

建立並返回此對象的一個副本。對於任何對象x,表達式:

(1)x.clone() != x爲true

(2)x.clone().getClass() == x.getClass()爲true

(3)x.clone().equals(x)通常狀況下爲true,但這並非必需要知足的要求

 

克隆實例

把上面例子的main函數修改一下:

public static void main(String[] args) throws Exception
{
    SimpleObject so0 = new SimpleObject();
    so0.setStr("111");
    SimpleObject so1 = (SimpleObject)so0.clone();
    
    System.out.println("so0 == so1?" + (so0 == so1));
    System.out.println("so0.getClass() == so1.getClass()?" + (so0.getClass() == so1.getClass()));
    System.out.println("so0.equals(so1)?" + (so0.equals(so1)));
        
    so1.setStr("222");
    System.out.println("so0.getStr():" + so0.getStr());
    System.out.println("so1.getStr():" + so1.getStr());
}

看一下運行結果:

Enter SimpleObject.constructor()
so0 == so1?false
so0.getClass() == so1.getClass()?true
so0.equals(so1)?false
so0.getStr():111
so1.getStr():222

獲得三個結論:

一、克隆一個對象並不會調用對象的構造方法,由於"Enter SimpleObject.constructor()"語句只出現了一次

二、符合JDK API的clone()方法三條規則

三、so1對於SimpleObject對象str字段的修改不再會影響到so0了

 

淺克隆和深克隆

淺克隆(shallow clone)和深克隆(deep clone)反映的是,當對象中還有對象的時候,那麼:

一、淺克隆,即很表層的克隆,若是咱們要克隆對象,只克隆它自身以及它所包含的全部對象的引用地址

二、深克隆,克隆除自身對象之外的全部對象,包括自身所包含的全部對象實例

這兩個概念應該很好理解,就不寫代碼了。多提一句,全部的基本數據類型,不管是淺克隆仍是深克隆,都會進行原值克隆,畢竟它們都不是對象,不是存儲在堆中的。

那其實Object的clone()方法,提供的是一種淺克隆的機制,若是想要實現對對象的深克隆,在不引入第三方jar包的狀況下,可使用兩種辦法:

一、先對對象進行序列化,緊接着立刻反序列化出

二、先調用super.clone()方法克隆出一個新對象來,而後在子類的clone()方法中手動給克隆出來的非基本數據類型(引用類型)賦值,好比ArrayList的clone()方法:

public Object clone() {
try {
    ArrayList<E> v = (ArrayList<E>) super.clone();
    v.elementData = Arrays.copyOf(elementData, size);
    v.modCount = 0;
    return v;
} catch (CloneNotSupportedException e) {
    // this shouldn't happen, since we are Cloneable
    throw new InternalError();
}
}
相關文章
相關標籤/搜索