這篇包含三篇文章的轉(zhuǎn)載和摘抄一段effective java的code
第一篇轉(zhuǎn)載
大家都知道Serializable是一個(gè)mark interface,告訴JVM這個(gè)對(duì)象可以被轉(zhuǎn)換成二進(jìn)制流來傳輸.
但是Serializable與Externalizable的轉(zhuǎn)換二進(jìn)制流的過程是不一樣的.
Serializable 在我們實(shí)現(xiàn)這個(gè)接口的時(shí)候,我們可以使用4個(gè)私有方法來控制序列化的過程:
我們來看一個(gè)例子:
Java代碼
1: public class FooImpl implements java.io.Serializable{
2: private String message;
3:
4: public String getFoo() {
5: return message;
6: }
7:
8: public void setMessage(String message) {
9: this.message = message;
10: }
11:
12: private void writeObject(java.io.ObjectOutputStream out) throws IOException {
13: System.out.println("writeObject invoked");
14: out.writeObject(this.message == null ? "hohohahaha" : this.message);
15: }
16:
17: private void readObject(java.io.ObjectInputStream in) throws IOException,
18: ClassNotFoundException {
19: System.out.println("readObject invoked");
20: this.message = (String) in.readObject();
21: System.out.println("got message:" + message);
22: }
23:
24: private Object writeReplace() throws ObjectStreamException {
25: System.out.println("writeReplace invoked");
26: return this;
27: }
28:
29: private Object readResolve() throws ObjectStreamException {
30: System.out.println("readResolve invoked");
31: return this;
32: }
33:
34: public Object serialize() throws IOException, ClassNotFoundException {
35: ByteArrayOutputStream baos = new ByteArrayOutputStream();
36: ObjectOutputStream oos = new ObjectOutputStream(baos);
37: oos.writeObject(this);
38: ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
39: ObjectInputStream ois = new ObjectInputStream(bais);
40: return ois.readObject();
41: }
42: public static void main(String[] args) throws IOException,
43: ClassNotFoundException {
44: FooImpl fooimpl = new FooImpl();
45: fooimpl.serialize();
46: }
我們運(yùn)行這段代碼看到的debug信息:
writeReplace invoked
writeObject invoked
readObject invoked
readResolve invoked
當(dāng)進(jìn)行序列化的時(shí)候:
首先JVM會(huì)先調(diào)用writeReplace方法,在這個(gè)階段,我們可以進(jìn)行張冠李戴,將需要進(jìn)行序列化的對(duì)象換成我們指定的對(duì)象.
跟著JVM將調(diào)用writeObject方法,來將對(duì)象中的屬性一個(gè)個(gè)進(jìn)行序列化,我們可以在這個(gè)方法中控制住哪些屬性需要序列化.
當(dāng)反序列化的時(shí)候:
JVM會(huì)調(diào)用readObject方法,將我們剛剛在writeObject方法序列化好的屬性,反序列化回來.
然后在readResolve方法中,我們也可以指定JVM返回我們特定的對(duì)象(不是剛剛序列化回來的對(duì)象).
注意到在writeReplace和readResolve,我們可以嚴(yán)格控制singleton的對(duì)象,在同一個(gè)JVM中完完全全只有唯一的對(duì)象,控制不讓singleton對(duì)象產(chǎn)生副本.
Externalizable 是一個(gè)有實(shí)際方法需要實(shí)現(xiàn)的interface,包括writeExternal和readExternal:
Java代碼
1: public class FooImpl implements java.io.Externalizable {
2: private String message;
3:
4: public String getFoo() {
5: return message;
6: }
7:
8: public void setMessage(String message) {
9: this.message = message;
10: }
11:
12: private Object writeReplace() throws ObjectStreamException {
13: System.out.println("writeReplace invoked");
14: return this;
15: }
16:
17: private Object readResolve() throws ObjectStreamException {
18: System.out.println("readResolve invoked");
19: return this;
20: }
21:
22: public Object serialize() throws IOException, ClassNotFoundException {
23: ByteArrayOutputStream baos = new ByteArrayOutputStream();
24: ObjectOutputStream oos = new ObjectOutputStream(baos);
25: oos.writeObject(this);
26: ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
27: ObjectInputStream ois = new ObjectInputStream(bais);
28: return ois.readObject();
29: }
30:
31: public void readExternal(ObjectInput arg0) throws IOException,
32: ClassNotFoundException {
33: System.out.println("readExternal invoked");
34: Object obj = arg0.readObject();
35: }
36:
37: public void writeExternal(ObjectOutput arg0) throws IOException {
38: System.out.println("writeExternal invoked");
39: arg0.writeObject("Hello world");
40: }
41: public static void main(String[] args) throws IOException,
42: ClassNotFoundException {
43: FooImpl fooimpl = new FooImpl();
44: fooimpl.serialize();
45: }
46: }
我們運(yùn)行這段代碼看到的debug信息:
writeReplace invoked
writeExternal invoked
readExternal invoked
readResolve invoked
在此writeExternal 和readExternal 的作用與writeObject和readObject 一樣.
最后,當(dāng)我們同時(shí)實(shí)現(xiàn)了兩個(gè)interface的時(shí)候,JVM只運(yùn)行Externalizable 接口里面的writeExternal 和readExternal 方法對(duì)序列化內(nèi)容進(jìn)行處理.
需要注意的是:Serializable是一個(gè)真正的mark interface,writeObject,readObject, writeReplace,readResolve是直接與JVM通信,告訴JVM序列化的內(nèi)容.
第二篇轉(zhuǎn)載
1) 實(shí)現(xiàn)了Serializable接口的類的改動(dòng)變得很難(兼容性問題)。
如果你想保持向下兼容性的話,這意味著要求新版本反序列化老版本序列化的數(shù)據(jù)流;如果你想保持向上兼容性的話,這意味著要求老版本反序列化新版本的數(shù)據(jù)流。
尤其是向下兼容性的問題帶來了讓我們的代碼實(shí)現(xiàn)很難改動(dòng)的問題,為了能在新版本中反序列化老版本的數(shù)據(jù)流,類的實(shí)現(xiàn)必須要保留老版本的實(shí)現(xiàn)過程。這無疑是我們給自己加帶上的一副枷鎖。
2) 實(shí)現(xiàn)了Serializable接口的類的測(cè)試成本大大增加了可以想象,當(dāng)新版本發(fā)布時(shí),為了保持兼容性,你的測(cè)試量將是版本數(shù)的平方!
3) 草率的接受默認(rèn)的序列化方式可能會(huì)帶來性能問題,甚至更糟。
4) 序列化作為額外的一種“構(gòu)造函數(shù)”可能破壞數(shù)據(jù)的完整性、約束性,甚至,一個(gè)精心偽造的數(shù)據(jù)流所序列化出的對(duì)象可能帶來安全隱患。
當(dāng)一個(gè)父類實(shí)現(xiàn)Serializable接口后,他的子類都將自動(dòng)的實(shí)現(xiàn)序列化。
1. 當(dāng)序列化遇到繼承時(shí)引發(fā)的問題?
序列化類的所有子類本身都是可序列化的。但是,如果父類是非序列化的,要求子類是可序列化的,該怎么辦呢?
"To allow subtypes of non-serializable classes to be serialized, the subtype may assume responsibility for saving and restoring the state of the supertype's public, protected, and (if accessible) package fields. The subtype may assume this responsibility only if the class it extends has an accessible no-arg constructor to initialize the class's state. It is an error to declare a class Serializable if this is not the case. The error will be detected at runtime. "(允許非序列化類的子類型序列化,僅當(dāng)該類的擴(kuò)展子類有一個(gè)可訪問的無參數(shù)的構(gòu)造函數(shù)用來初始化該類的狀態(tài)時(shí),其子類可以負(fù)責(zé)保存和恢復(fù)父類型的公有 的、保護(hù)的和(如果可訪問)包的域的狀態(tài)。否則的話聲明其子類是可序列化的將會(huì)發(fā)生錯(cuò)誤,該錯(cuò)誤在運(yùn)行的時(shí)候被檢測(cè))
也就是說,要為一個(gè)沒有實(shí)現(xiàn)Serializable接口的父類,編寫一個(gè)能夠序列化的子類要做兩件事情:
其一,父類要有一個(gè)無參的constructor。
1: public class ParentClass {
2: int parentValue;
3: public ParentClass(); //如果要求其子類可以序列化,該無參數(shù)構(gòu)造函數(shù)是必須的
4: public ParentClass(int parentValue) {
5: this.parentValue = parentValue;
6: }
7: public String toString() {
8: return "Parent Value:" + parentValue;
9: }
10: }
其二,子類要負(fù)責(zé)序列化(反序列化)父類的域。
子 類序列化父類域可以通過自定義序列化實(shí)現(xiàn)。自定義序列化要求類中必須有writeObject和readObject方法,它們的定義必須是如下形 式,writeObject方法中必須首先調(diào)用ObjectOutputStream類的defaultWriteObject方法處理默認(rèn)的序列化,其 它的序列化在其后進(jìn)行處理;readObject方法讀取的順序必須和寫入的順序一樣,即首先通過objectInputStream的 defaultReadObject方法讀取默認(rèn)的序列化,再依次讀出其它的自定義序列化信息。
1: private void writeObject(ObjectOutputStream s) throws IOException {
2: s.defaultWriteObject();
3: // customized serialization code
4: }
5:
6: private void readObject(ObjectInputStream s) throws IOException {
7: s.defaultReadObject();
8: // customized deserialization code
9: // followed by code to update the object, if necessary
10: }
11: 如果可序列化類中包含正確的writeObject/readObject函數(shù)對(duì),進(jìn)行序列化和反序列化的時(shí)候,writeObject/readObject就會(huì)被調(diào)用,以代替默認(rèn)的序列化行為
12: public class ChildClass Extends ParantClass implements Serializable {
13: int subvalue;
14: public ChildClass(int supervalue,int subvalue) {
15: super(supervalue);
16: this.subvalue=subvalue;
17: }
18:
19: public String toString() {
20: return super.toString()+" sub: "+subvalue;
21: }
22:
23: private void writeObject(java.io.ObjectOutputStream out) throws IOException{
24: out.defaultWriteObject();//先序列化對(duì)象
25: out.writeInt(supervalue);//再序列化父類的域
26: }
27:
28: private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
29: in.defaultReadObject();//先反序列化對(duì)象
30: supervalue=in.readInt();//再反序列化父類的域
31: }
32: }
33:
2. 何時(shí)接受默認(rèn)的java序列化?
java默認(rèn)的序列化行為,java將一切關(guān)于對(duì)象的信息都保存了下了,也就是說,有些時(shí)候那些不需要保存的也被保存了下來。一般情況下,我們僅僅需要保存邏輯數(shù)據(jù)就可以了。不需要保存的數(shù)據(jù)我們可以用關(guān)鍵字transient標(biāo)出。
以下是一個(gè)例子:
1: import java.io.*;
2: public class Serial implements Serializable {
3: int company_id;
4: String company_addr;
5: transient boolean company_flag;
6: }
則company_flag字段將不會(huì)參與序列化與反序列化,但同時(shí)也增加了為它初始值的責(zé)任。這也是序列化常常導(dǎo)致的問題之一。因?yàn)樾蛄谢喈?dāng)于一個(gè)只 接受數(shù)據(jù)流的public構(gòu)造函數(shù),這種對(duì)象構(gòu)造方法是語言之外的。但他仍然是一種形式上的構(gòu)造函數(shù)。如若你的類不能夠通過其他方面來保證初始化,則你需 要額外的提供readObject方法,首先正常的反序列化,然后對(duì)transient標(biāo)示的字段進(jìn)行初始化。
第三篇轉(zhuǎn)載
Serialization is a handy and powerful aspect of Java. Being able to persist objects onto disk and read them later is one of the most under-used features of Java I think. In the base cases, serialization can 'just work'. However, as more complicated object formats and design patterns are adopted, the likelihood that 'transparent' object serialization will 'just work' becomes less and less likely. One case where serialization needs a little help is when dealing with a controlled set of instances - such as singletons and enumerations.
Whenever a singleton is serializable, it's important to ensure that the singleton instance is used. This is done through the readResolve method. For instance, a singleton may look like this:
1: public final class MySingleton {
2: private MySingleton() { }
3: private static final MySingleton INSTANCE = new MySingleton();
4: public static MySingleton getInstance() { return INSTANCE; }
5: }
In the above example, there is only one way to get an instance of MySingleton - that is to use the getInstance() method. Unfortunately, this code becomes 'broken' simply by adding one interface implementation:
public final class MySingleton implements Serializable {
//...
Now through the serializable tools, someone can write a singleton instance to disk, and then read it back up, effectively getting a new instance. Even though the constructor is private, the serializable tools have special access to create instances of a class regardless. Serialization has a special hook it uses - a private method on the class being instantiated called readResolve() - which is meant to supply a 'hook' for a class developer to ensure that they have a say in what object is returned by serialization. Oddly enough, readResolve() is not static, but is instead invoked on the new instance just created by the serialization. We'll get into that in a minute - for now, here is how our readResolve() method works with our singleton:
1: public final class MySingleton {
2: private MySingleton() { }
3: private static final MySingleton INSTANCE = new MySingleton();
4: public static MySingleton getInstance() { return INSTANCE; }
5: private Object readResolve() throws ObjectStreamException {
6: // instead of the object we're on,
7: // return the class variable INSTANCE
8: return INSTANCE;
9: }
10: }
So far so good. Things get a little complicated when dealing with more than one instance however. To explain this, I'll show this using a type-safe enumeration. Keep in mind that Java 5's enum type automatically handles this readResolve case for you. Here is a nice little enumeration:
1: public final class Sides {
2: private int value;
3: private Sides(int newVal) { value = newVal; }
4: private static final int LEFT_VALUE = 1;
5: private static final int RIGHT_VALUE = 2;
6: private static final int TOP_VALUE = 3;
7: private static final int BOTTOM_VALUE = 4;
8: public static final LEFT = new Sides(LEFT_VALUE);
9: public static final RIGHT = new Sides(RIGHT_VALUE);
10: public static final TOP = new Sides(TOP_VALUE);
11: public static final BOTTOM = new Sides(BOTTOM_VALUE);
12: }
Now, implementing serialization, the key to determining which instance to return is in inspecting what value is set on the object itself:
1: public final class Sides implements Serializable {
2: private int value;
3: private Sides(int newVal) { value = newVal; }
4: private static final int LEFT_VALUE = 1;
5: private static final int RIGHT_VALUE = 2;
6: private static final int TOP_VALUE = 3;
7: private static final int BOTTOM_VALUE = 4;
8: public static final LEFT = new Sides(LEFT_VALUE);
9: public static final RIGHT = new Sides(RIGHT_VALUE);
10: public static final TOP = new Sides(TOP_VALUE);
11: public static final BOTTOM = new Sides(BOTTOM_VALUE);
12: private Object readResolve() throws ObjectStreamException {
13: // Switch on this instance's value to figure out which class variable
14: // this is meant to match
15: switch(value) {
16: case LEFT_VALUE: return LEFT;
17: case RIGHT_VALUE: return RIGHT;
18: case TOP_VALUE: return TOP;
19: case BOTTOM_VALUE: return BOTTOM;
20: }
21: return null;
22: }
23: }
自己的微小筆記
1. serialVersionUID: 如果新的class要兼容舊的,這個(gè)id必須是一樣的。 自己不寫這個(gè)field, 在runtime的時(shí)候會(huì)自動(dòng)生成
2. effective java里custom serialization的一個(gè)例子:
1: // StringList with a reasonable custom serialized form
2: public final class StringList implements Serializable {
3: private transient int size = 0;
4: private transient Entry head = null;
5: // No longer Serializable!
6: private static class Entry {
7: String data;
8: Entry next;
9: Entry previous;
10: }
11: // Appends the specified string to the list
12: public final void add(String s) { ... }
13: /**
14: * Serialize this {@code StringList} instance.
15: *
16: * @serialData The size of the list (the number of strings
17: * it contains) is emitted ({@code int}), followed by all of
18: * its elements (each a {@code String}), in the proper
19: * sequence.
20: */
21: private void writeObject(ObjectOutputStream s)
22: throws IOException {
23: s.defaultWriteObject();
24: s.writeInt(size);
25: // Write out all elements in the proper order.
26: for (Entry e = head; e != null; e = e.next)
27: s.writeObject(e.data);
28: }
29: private void readObject(ObjectInputStream s)
30: throws IOException, ClassNotFoundException {
31: s.defaultReadObject();
32: int numElements = s.readInt();
33: // Read in all elements and insert them in list
34: for (int i = 0; i < numElements; i++)
35: add((String) s.readObject());
36: }
37: ... // Remainder omitted
38: }