java对象序列化克隆_Java8基础知识(三)对象克隆与序列化
對象克隆
對象克隆最簡單的方式是:將對原對象的引用直接傳給一個新的副本變量。這種方式存在很大的缺陷,兩個變量中任何一個變量的改變都會影響另一個變量。
淺拷貝
利用Object類的clone方法,能夠創(chuàng)建一個新的對象,并拷貝原對象的域 ,返回新對象的引用。
優(yōu)點:使副本的操作與原變量的操作相對獨立。
缺點:當(dāng)淺克隆對象與原對象共享的子對象可變時,對可變子對象的操作仍會同時影響兩個對象。
// 原對象,包含Date類子對象
Employee original = new Employee("John Public", 50000);
// 使用clone方法,兩個對象共享對Date類子對象的引用
Employee copy = original.clone();
// 若修改Date類子對象,則兩個對象中的Date類子對象的值都會改變
在上述情況中,由于Date類是可變的,導(dǎo)致淺克隆對象和原對象之間仍然存在影響。作為替代,應(yīng)當(dāng)使用不可變的LocalDate類。
Cloneable接口
實際上,Object類中的clone方法被聲明為protected。因此,Object的子類只能調(diào)用clone來克隆它們自己的對象,而不能克隆其他類的對象。
注意:Cloneable接口是Java提供的標記接口。標記接口不包含任何方法,其唯一的作用是允許在類型查詢中使用instanceof。
即使clone的默認實現(xiàn)(淺拷貝)能夠滿足要求,仍然需要實現(xiàn)Cloneable接口,并將clone重新定義為public,再調(diào)用super.clone()。
class Employee implements Cloneable {
// 如果調(diào)用clone的類沒有實現(xiàn)Cloneable接口,則拋出CloneNotSupportedException異常
public Employee clone() throws CloneNotSupportedException {
// 調(diào)用超類的clone方法
return (Employee) super.clone();
}
}
深拷貝
當(dāng)淺拷貝無法滿足對象之間的獨立性時,需要建立深拷貝,即克隆對象中可變的實例域。
class Employee implements Cloneable {
// 如果調(diào)用clone的類沒有實現(xiàn)Cloneable接口,則拋出CloneNotSupportedException異常
public Employee clone() throws CloneNotSupportedException {
// 調(diào)用超類的clone方法
Employee cloned = super.clone();
// 對可變的hireDay子對象再次建立淺拷貝
cloned.hireDay = (Date) hireDay.clone();
return cloned;
}
}
序列化
對象序列化是Java語言支持的一種非常通用的機制,它可以將任何對象寫出到輸出流中,并在之后將其讀回。使用對象序列化機制可能便捷地處理多態(tài)集合(例如,一個Employee記錄數(shù)組包含Manager等子類實例)。
保存和加載序列化對象
使用ObjectOutputStream對象的writeObject方法保存數(shù)據(jù)對象,使用ObjectInputStream對象的readObject讀回數(shù)據(jù)對象。使用序列化時必須實現(xiàn)Serializable接口,其和Cloneable接口很相似,沒有任何方法,其他類不需要為實現(xiàn)它做出任何改動。
// 創(chuàng)建一個Employee對象和一個Manager對象
Employee harry = new Employee("Harry Hacker", 50000, 1989, 10, 1);
Manager boss = new Manager("Carl Cracker", 80000, 1987, 12, 15);
// 將兩個對象存儲在對象輸出流中
ObjectOutputStream out = new PbjectOutputStream(new FileOutputStream("employee.dat"));
out.writeObject(harry);
out.writeObject(boss);
// 將兩個對象從對象輸入流中恢復(fù)
ObjectInputStream in = new ObjectInputStream(new FileInputStream("employee.dat"));
Employee e1 = (Employee) in.readObject();
Employee e2 = (Employee) in.readObject();
對象序列化工作機制如下:
每一個對象引用關(guān)聯(lián)一個序列號。
第一次遇到每個對象時,保存其對象數(shù)據(jù)到輸出流中。
如果某個對象之前已經(jīng)被保存過,那么只寫出“與之前保存過的序列號為x的對象相同”。
讀回時,過程相反:對于對象輸入流中的對象,在第一次遇到其序列號時,構(gòu)建它,并使用流中數(shù)據(jù)來初始化它,然后記錄這個序列號和新對象之間的關(guān)聯(lián)。
當(dāng)遇到“與之前保存過的序列號x的對象相同”標記時,獲取與這個序列號相關(guān)聯(lián)的對象引用。
注:序列化使用序列號代替了內(nèi)存地址,允許將對象集合在機器之間進行傳送。
對象序列化的文件格式
注:以下(x)格式均表示字節(jié)長度。
對象序列化文件開頭:魔幻數(shù)字(2) + 版本號(2)
類描述符的存儲格式:
72 + 類名長度(2) + 類名
指紋(8) + 標志(1) + 數(shù)據(jù)域描述符的數(shù)量(2) + 數(shù)據(jù)域描述符 + 78 + 超類類型(無超類為70)
其中,指紋指通過對類、超類、接口、域類型和方法簽名按照規(guī)范方式排序,然后應(yīng)用安全散列算法SHA得到的。SHA將數(shù)據(jù)塊轉(zhuǎn)換為20個字節(jié)的數(shù)據(jù)包,序列化機制只使用SHA的前8個字節(jié)。
標志字節(jié)由java.io.ObjectStreamConstants中定義的3位掩碼構(gòu)成:
static final byte SC_WRITE_METHOD = 1;
// class has a writeObject_method that writes additional data
static final byte SC_SERIALIZABLE = 2;
// class implements the Serializable interface
static final byte SC_EXTERNALIZABLE = 4;
// class implements the Externalizable interface
數(shù)據(jù)域描述符的格式如下:
類型編碼(1) + 域名長度(2) + 域名 + 類名(若域是對象)
對象序列化的文件格式特點如下:
對象流輸出中包含所有對象的類型和數(shù)據(jù)域。
每個對象都被賦予一個序列號。
相同對象的重復(fù)出現(xiàn)將被存儲為對這個對象的序列號的引用。
修改默認的序列化機制
對于不可序列化的數(shù)據(jù)域,如只對本地方法有意義的存儲文件句柄值等,序列化機制將其標記為transient。不可序列化的類的域也需要標記為transient。瞬時的域在對象序列化時被跳過。
在可序列化的類中可以定義writeObject方法和readObject方法:
private void writeObject(ObjectOutputStream out)
throws IOException;
private void readObject(ObjectInputStream in)
throws IOException, ClassNotFoundException;
定義上述方法的類在序列化時會調(diào)用這兩個方法對其數(shù)據(jù)域進行序列化。
由于writeObject和readObject方法不關(guān)心超類數(shù)據(jù)和任何其他類的信息,若要對整個對象的存儲和恢復(fù)負責(zé),就要實現(xiàn)Externalizable接口,定義writeExternal方法和readExternal方法:
public void writeExternal(ObjectOutputStream out)
throws IOException;
public void readExternal(ObjectInputStream in)
throws IOException, ClassNoutFoundException;
注意:writeObject和readObject方法是私有的,且只能被序列化機制調(diào)用。而writeExternal和readExternal方法是共有的,且readExternal方法甚至潛在地允許修改現(xiàn)有對象的狀態(tài)。
使用序列化技術(shù)克隆對象
只需將對象序列化到輸出流中,然后將其讀回,就能產(chǎn)生現(xiàn)有對象的深拷貝。在此過程中,可以使用ByteArrayOutputStream方法將數(shù)據(jù)保存到字節(jié)數(shù)組中。如下修改clone方法:
class SerialCloneable implements Cloneable, Serializable {
public Object clone() throws CloneNotSupportedException {
try {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
try (ObjectOutputStream = new ObjectOutputStream(bout)) {
out.writeObject(this);
}
try (InputSteam bin = new ByteArrayInputStream(bout.toByteArray())) {
ObjectInputStream in = new ObjectInputStream(bin);
return in.readObject();
}
catch (IOException | ClassNotFoundException e) {
CloneNotSupportedException e2 = new CloneNotSupportException();
e2.initCause(e);
throw e2;
}
}
}
}
需要克隆的類只需繼承SerialCloneable類即可。
創(chuàng)作挑戰(zhàn)賽新人創(chuàng)作獎勵來咯,堅持創(chuàng)作打卡瓜分現(xiàn)金大獎總結(jié)
以上是生活随笔為你收集整理的java对象序列化克隆_Java8基础知识(三)对象克隆与序列化的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 喜马拉雅极速版怎么取消自动续费
- 下一篇: 喜马拉雅极速版怎么设置不被其他程序打断