1. 程式人生 > 實用技巧 >Java物件克隆

Java物件克隆

  想對一個物件進行處理,又想保留原有的資料進行接下來的操作,就需要克隆了,Java語言中克隆針對的是類的例項。

一、實現Cloneable介面

  

package Base;

public class SimpleObject implements Cloneable{
    private String str;

    public String getStr()
    {
        return str;
    }

    public void setStr(String str)
    {
        this.str = str;
    }

    
public SimpleObject() { System.out.println("Enter SimpleObject.constructor()"); } public Object clone() throws CloneNotSupportedException { return super.clone(); } }
package Base;

public class Test1 {
    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()); } }

  • 實現了Cloneable介面,以指示Object的clone()方法可以合法地對該類例項進行按欄位複製
  • 如果在沒有實現Cloneable介面的例項上呼叫Object的clone()方法,則會導致丟擲CloneNotSupporteddException
  • 按照慣例,實現此介面的類應該使用公共方法重寫Object的clone()方法,Object的clone()方法是一個受保護的方法

淺克隆和深克隆

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

1、淺克隆,即很表層的克隆,如果我們要克隆物件,只克隆它自身以及它所包含的所有物件的引用地址

2、深克隆,克隆除自身物件以外的所有物件,包括自身所包含的所有物件例項

這兩個概念應該很好理解,就不寫程式碼了。多提一句,所有的基本資料型別,無論是淺克隆還是深克隆,都會進行原值克隆,畢竟它們都不是物件,不是儲存在堆中的。

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

1、先對物件進行序列化,緊接著馬上反序列化出

2、先呼叫super.clone()方法克隆出一個新物件來,然後在子類的clone()方法中手動給克隆出來的非基本資料型別(引用型別)賦值,比如ArrayList的clone()方法:

二、實現Serializable介面

  • 當你想把的記憶體中的物件狀態儲存到一個檔案中或者資料庫中時候;
  • 當你想用套接字在網路上傳送物件的時候;
  • 當你想通過RMI傳輸物件的時候;

  序列化和反序列化的定義:Java序列化就是指把Java物件轉換為位元組序列的過程,Java反序列化就是指把位元組序列恢復為Java物件的過程。

  序列化最重要的作用:在傳遞和儲存物件時.保證物件的完整性和可傳遞性。物件轉換為有序位元組流,以便在網路上傳輸或者儲存在本地檔案中。

  反序列化的最重要的作用:根據位元組流中儲存的物件狀態及描述資訊,通過反序列化重建物件。

  總結:核心作用就是物件狀態的儲存和重建。(整個過程核心點就是位元組流中所儲存的物件狀態及描述資訊)

實現過程

  1. 實現序列化的必備要求:只有實現了Serializable或者Externalizable介面的類的物件才能被序列化為位元組序列。(不是則會丟擲異常)
  2. JDK中序列化和反序列化的API:①java.io.ObjectInputStream:物件輸入流。該類的readObject()方法從輸入流中讀取位元組序列,然後將位元組序列反序列化為一個物件並返回。   ②java.io.ObjectOutputStream:物件輸出流。該類的writeObject(Object obj)方法將將傳入的obj物件進行序列化,把得到的位元組序列寫入到目標輸出流中進行輸出。
  3. 實現序列化和反序列化的三種實現:

    ①若Student類僅僅實現了Serializable介面,則可以按照以下方式進行序列化和反序列化。

    ObjectOutputStream採用預設的序列化方式,對Student物件的非transient的例項變數進行序列化。
    ObjcetInputStream採用預設的反序列化方式,對Student物件的非transient的例項變數進行反序列化。

    ②若Student類僅僅實現了Serializable介面,並且還定義了readObject(ObjectInputStream in)和writeObject(ObjectOutputSteam out),則採用以下方式進行序列化與反序列化。

    ObjectOutputStream呼叫Student物件的writeObject(ObjectOutputStream out)的方法進行序列化。
    ObjectInputStream會呼叫Student物件的readObject(ObjectInputStream in)的方法進行反序列化。

    ③若Student類實現了Externalnalizable介面,且Student類必須實現readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法,則按照以下方式進行序列化與反序列化。

    ObjectOutputStream呼叫Student物件的writeExternal(ObjectOutput out))的方法進行序列化。
    ObjectInputStream會呼叫Student物件的readExternal(ObjectInput in)的方法進行反序列化。

package Base;

import java.io.Serializable;

public class Student implements Serializable {
    private String userName;
    private String password;
    private String year;

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getYear() {
        return year;
    }

    public void setYear(String year) {
        this.year = year;
    }

    public Student(String userName, String password, String year) {
        this.userName = userName;
        this.password = password;
        this.year = year;
    }
}
package Base;

import java.io.*;

public class Test {
    public static void main(String[] args) throws IOException, ClassNotFoundException{
        //序列化
        FileOutputStream fos = new FileOutputStream("object.out");
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        Student student1 = new Student("lihao", "wjwlh", "21");
        oos.writeObject(student1);
        oos.flush();
        oos.close();
        //反序列化
        FileInputStream fis = new FileInputStream("object.out");
        ObjectInputStream ois = new ObjectInputStream(fis);
        Student student2 = (Student) ois.readObject();
        System.out.println(student2.getUserName()+ " " +
                student2.getPassword() + " " + student2.getYear());


    }
}

  序列化執行時使用一個稱為 serialVersionUID 的版本號與每個可序列化類相關聯,該序列號在反序列化過程中用於驗證序列化物件的傳送者和接收者是否為該物件載入了與序列化相容的類。為它賦予明確的值。顯式地定義serialVersionUID有兩種用途:

  • 在某些場合,希望類的不同版本對序列化相容,因此需要確保類的不同版本具有相同的serialVersionUID
  • 在某些場合,不希望類的不同版本對序列化相容,因此需要確保類的不同版本具有不同的serialVersionUID

  Java有很多基礎類已經實現了serializable介面,比如String,Vector等。但是也有一些沒有實現serializable介面的

  如果一個物件的成員變數是一個物件,那麼這個物件的資料成員也會被儲存!這是能用序列化解決深拷貝的重要原因