主要学习下:
1. writeReplace和readResolve
2. 父类,子类里的序列化问题
一.学习下writeReplace和readResolve
大家都知道Serializable是一个mark interface,告诉JVM这个对象可以被转换成二进制流来传输.
但是Serializable与Externalizable的转换二进制流的过程是不一样的.
Serializable 在我们实现这个接口的时候,我们可以使用4个私有方法来控制序列化的过程:
我们来看一个例子:
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: }
47: }
我们运行这段代码看到的debug信息:
writeReplace invoked
writeObject invoked
readObject invoked
readResolve invoked
当进行序列化的时候:
首先JVM会先调用writeReplace方法,在这个阶段,我们可以进行张冠李戴,将需要进行序列化的对象换成我们指定的对象.
跟着JVM将调用writeObject方法,来将对象中的属性一个个进行序列化,我们可以在这个方法中控制住哪些属性需要序列化.
当反序列化的时候:
JVM会调用readObject方法,将我们刚刚在writeObject方法序列化好的属性,反序列化回来.
然后在readResolve方法中,我们也可以指定JVM返回我们特定的对象(不是刚刚序列化回来的对象).
注意到在writeReplace和readResolve,我们可以严格控制singleton的对象,在同一个JVM中完完全全只有唯一的对象,控制不让singleton对象产生副本.
Externalizable 是一个有实际方法需要实现的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: }
我们运行这段代码看到的debug信息:
writeReplace invoked
writeExternal invoked
readExternal invoked
readResolve invoked
在此writeExternal 和readExternal 的作用与writeObject和readObject 一样.
最后,当我们同时实现了两个interface的时候,JVM只运行Externalizable 接口里面的writeExternal 和readExternal 方法对序列化内容进行处理.
需要注意的是:Serializable是一个真正的mark interface,writeObject,readObject, writeReplace,readResolve是直接与JVM通信,告诉JVM序列化的内容.
二. 父类,子类序列化再学习
1) 实现了Serializable接口的类的改动变得很难(兼容性问题)。
如果你想保持向下兼容性的话,这意味着要求新版本反序列化老版本序列化的数据流;如果你想保持向上兼容性的话,这意味着要求老版本反序列化新版本的数据流。
尤其是向下兼容性的问题带来了让我们的代码实现很难改动的问题,为了能在新版本中反序列化老版本的数据流,类的实现必须要保留老版本的实现过程。这无疑是我们给自己加带上的一副枷锁。
2) 实现了Serializable接口的类的测试成本大大增加了可以想象,当新版本发布时,为了保持兼容性,你的测试量将是版本数的平方!
3) 草率的接受默认的序列化方式可能会带来性能问题,甚至更糟。
4) 序列化作为额外的一种“构造函数”可能破坏数据的完整性、约束性,甚至,一个精心伪造的数据流所序列化出的对象可能带来安全隐患。
当一个父类实现Serializable接口后,他的子类都将自动的实现序列化。
1. 当序列化遇到继承时引发的问题?
序列化类的所有子类本身都是可序列化的。但是,如果父类是非序列化的,要求子类是可序列化的,该怎么办呢?
"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. "(允许非序列化类的子类型序列化,仅当该类的扩展子类有一个可访问的无参数的构造函数用来初始化该类的状态时,其子类可以负责保存和恢复父类型的公有 的、保护的和(如果可访问)包的域的状态。否则的话声明其子类是可序列化的将会发生错误,该错误在运行的时候被检测)
也就是说,要为一个没有实现Serializable接口的父类,编写一个能够序列化的子类要做两件事情:
其一,父类要有一个无参的constructor。
1: public class ParentClass {
2: int parentValue;
3: public ParentClass(); //如果要求其子类可以序列化,该无参数构造函数是必须的
4: public ParentClass(int parentValue) {
5: this.parentValue = parentValue;
6: }
7: public String toString() {
8: return "Parent Value:" + parentValue;
9: }
10: }
其二,子类要负责序列化(反序列化)父类的域。
子 类序列化父类域可以通过自定义序列化实现。自定义序列化要求类中必须有writeObject和readObject方法,它们的定义必须是如下形 式,writeObject方法中必须首先调用ObjectOutputStream类的defaultWriteObject方法处理默认的序列化,其 它的序列化在其后进行处理;readObject方法读取的顺序必须和写入的顺序一样,即首先通过objectInputStream的 defaultReadObject方法读取默认的序列化,再依次读出其它的自定义序列化信息。
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函数对,进行序列化和反序列化的时候,writeObject/readObject就会被调用,以代替默认的序列化行为
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();//先序列化对象
25: out.writeInt(supervalue);//再序列化父类的域
26: }
27:
28: private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
29: in.defaultReadObject();//先反序列化对象
30: supervalue=in.readInt();//再反序列化父类的域
31: }
32: }
33:
2. 何时接受默认的java序列化?
java默认的序列化行为,java将一切关于对象的信息都保存了下了,也就是说,有些时候那些不需要保存的也被保存了下来。一般情况下,我们仅仅需要保存逻辑数据就可以了。不需要保存的数据我们可以用关键字transient标出。
以下是一个例子:
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字段将不会参与序列化与反序列化,但同时也增加了为它初始值的责任。这也是序列化常常导致的问题之一。因为序列化相当于一个只 接受数据流的public构造函数,这种对象构造方法是语言之外的。但他仍然是一种形式上的构造函数。如若你的类不能够通过其他方面来保证初始化,则你需 要额外的提供readObject方法,首先正常的反序列化,然后对transient标示的字段进行初始化。
三. 转载一遍英文版的序列化讲解(原味的理解透彻)
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要兼容旧的,这个id必须是一样的。 自己不写这个field, 在runtime的时候会自动生成
2. effective java里custom serialization的一个例子:
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: }
到此,把序列化,反序列化又更深得学习了小,是否还有更深得知识点,欢迎跟贴啊: )
以往学习贴总结:
7天Java学习---Java的对象序列化以及文件IO处理
Java序列化的机制和原理,以及自定义序列化问题