在分析Serializable之前掀潮,先來看看ObjectInputStream和ObjectOutputStream這兩個流對象兼蜈。
ObjectOutputStream:將對象轉(zhuǎn)化為流
ObjectInputStream:將流轉(zhuǎn)化為對象
下面舉個例子來說明ObjectInputStream和ObjectOutputStream的使用儒老。
public class Phone {
public String name;
public String address;
public Phone() {
System.out.println("=============1");
}
public Phone(String name, String address) {
System.out.println("=============2");
this.name = name;
this.address = address;
}
}
Phone類是測試類,調(diào)用方式如下:
public class MyClass {
public static void main(String[] args) throws IOException, ClassNotFoundException {
File file = new File("javatest/data/phone.txt");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(file));
Phone phone = new Phone("zhangsan", "beijing");
objectOutputStream.writeObject(phone);
objectOutputStream.close();
System.out.println("========================");
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(file));
Object object = objectInputStream.readObject();
objectInputStream.close();
if (object instanceof Phone) {
Phone phone1 = (Phone) object;
System.out.println(phone1.name);
}
System.out.println(object);
}
}
運(yùn)行后會出現(xiàn)如下錯誤日志:
Exception in thread "main" java.io.NotSerializableException: com.example.Phone
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
at com.example.MyClass.main(MyClass.java:18)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
日志信息提示說Phone沒有實(shí)現(xiàn)Serializable接口遣疯。按照提示將Phone方法做相應(yīng)的修改:
public class Phone implements Serializable{
public String name;
public String address;
public Phone() {
System.out.println("=============1");
}
public Phone(String name, String address) {
System.out.println("=============2");
this.name = name;
this.address = address;
}
}
繼續(xù)執(zhí)行main()方法休讳,查看日志信息:
=============2
========================
zhangsan
com.example.Phone@4dd8dc3
main()方法的主要流程分為如下幾步:
1 創(chuàng)建文件phone.txt
2 創(chuàng)建Phone實(shí)例,并初始化赵哲,然后通過ObjectOutputStream將Phone的實(shí)例寫入到phone.txt中
3 通過ObjectInputStream將phone.txt中的內(nèi)容讀出,并構(gòu)造Object的一個實(shí)例君丁,將數(shù)據(jù)放入Object的實(shí)例中枫夺。
phone.txt文件中的內(nèi)容如下:
aced 0005 7372 0011 636f 6d2e 6578 616d
706c 652e 5068 6f6e 6551 4868 16d4 8afd
8702 0002 4c00 0761 6464 7265 7373 7400
124c 6a61 7661 2f6c 616e 672f 5374 7269
6e67 3b4c 0004 6e61 6d65 7100 7e00 0178
7074 0007 6265 696a 696e 6774 0008 7a68
616e 6773 616e
如上是以byte的形式打開,下面來分析一下各個數(shù)據(jù)代表了什么意思(一下數(shù)據(jù)均為16進(jìn)制绘闷,省略了ox前綴):
aced:STREAM_MAGIC橡庞,聲明使用了序列化協(xié)議
0005:STREAM_VERSION,序列化協(xié)議版本
73:TC_OBJECT印蔗,聲明這是一個新的對象
72:TC_CLASSDESC扒最,聲明這里開始一個新Class
0011:Class名字的長度
636f 6d2e 6578 616d 706c 652e 5068 6f6e 65:類名,com.example.Phone
51 4868 16d4 8afd 87:SerialVersionUID, 序列化ID华嘹,如果沒有指定吧趣, 則會由算法隨機(jī)生成一個8byte的ID。
02:標(biāo)記號耙厚,該值聲明該對象支持序列化
0002:該類所包含的域個數(shù)
4c:L
00 07:域名字的長度
61 6464 7265 7373:address
74:TC_STRING强挫,代表一個new String,用String來引用對象
00 12:長度
4c:L
6a61 7661 2f6c 616e 672f 5374 7269 6e67 3b4c:java/lang/String;L
0004:域名字的長度
6e61 6d65:name
7100 7e00 01:待確認(rèn)
78:TC_ENDBLOCKDATA對象塊結(jié)束的標(biāo)志
70:沒有超類
74:TC_STRING
0007:address字段值的長度
6265 696a 696e 67:address字段的值beijing
74:TC_STRING
0008:name字段值的長度
7a68 616e 6773 616e : name字段的值zhangsan
文件中的內(nèi)容相當(dāng)豐富薛躬,僅僅憑這些內(nèi)容俯渤,已經(jīng)可以完整的復(fù)原Phone實(shí)例存儲前的狀態(tài)。
其實(shí)型宝,如上過程八匠,就是Java 序列化和反序列化的一個簡單的實(shí)現(xiàn)絮爷。
Serializable也能夠?qū)崿F(xiàn)自定義序列化過程。
重新定義Phone類如下:
public class Phone implements Serializable {
transient public String name;
public String address;
public Phone() {
System.out.println("=============1");
}
public Phone(String name, String address) {
System.out.println("=============2");
this.name = name;
this.address = address;
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
out.writeBytes(name);
System.out.println("=========write");
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
byte[] bytes = new byte[50];
in.read(bytes);
name = new String(bytes).trim();
System.out.println("=========read");
}
}
將name屬性添加了transient修飾梨树,添加了兩個方法writeObject()和readObject()坑夯。運(yùn)行main()方法,日志信息如下:
=============2
=========write
========================
=========read
zhangsan
com.example.Phone@58372a00
關(guān)于Serializable的使用劝萤,有幾點(diǎn)需要說明:
1 Serializable只是一個接口渊涝,本身沒有任何實(shí)現(xiàn)
2 對象的反序列化并沒有調(diào)用對象的任何構(gòu)造方法
3 serialVersionUID是用于記錄文件版本信息的,最好能夠自定義床嫌。否則跨释,系統(tǒng)會自動生成一個serialVersionUID,文件或者對象的任何改變厌处,都會改變serialVersionUID鳖谈,導(dǎo)致反序列化的失敗,如果自定義就沒有這個問題阔涉。
4 如果某個屬性不想實(shí)現(xiàn)序列化缆娃,可以采用transient修飾
5 Serializable的系統(tǒng)實(shí)現(xiàn)是采用ObjectInputStream和ObjectOutputStream實(shí)現(xiàn)的,這也是為什么調(diào)用ObjectInputStream和ObjectOutputStream時瑰排,需要對應(yīng)的類實(shí)現(xiàn)Serializable接口贯要。