JAVA 对象序列化(三)——transient以及Externalizable的一种替代方法(使用Serializable)
transient(瞬时)关键字
当我们队序列化进行控制时,可能某个特定子对象不想让Java的序列化机制自动保存与恢复。如果子对象表示的是我们不希望将其序列化的敏感信息(如密码),通常就会面临这种情况。即使对象中的这些信息是private属性,一经序列化处理,人们就可以通过读取文件或者拦截网络传输的方式来访问它。
在Java对象序列化(二)——Externalizable中我们通过将类实现为Externalizable可以实现类的部分序列化。本文提供了另一种可以实现部分序列化的方式,即通过关键字——transient(瞬时)。如果我们正在操作的是一个Serializable对象,那么所有序列化操作就会自动进行,为了能够给予控制,我们可以用transient关键字逐个字段的关闭序列化,它的意思“不烦恼您老保存或者恢复数据——我会自己处理的,谢谢。”
由于Externalizable对象在默认情况下不保存它的任何字段(即任何字段都不进行序列化处理),所以transient关键字只能和Serializable对象一起使用。
Externalizable的替代方法
如果不是特别坚持实现Externalizable接口,那么我们可以通过实现Serializable接口,并添加名为writeObject()和readObject()方法(注意:这里用的是添加而不是“覆盖”或者“实现”,因为这两个方法不是基类Object也不是接口Serializable中的方法)。这样一旦对象被序列化或者反序列还原,就会自动地分别调用者两个方法。也就是说,只要我们提供了这两个方法,就会使用它们而不是默认的序列化机制。
但是一定要注意,这两个方法的必须具有准确的方法特征签名,必须严格按照如下形式
private void writeObject(ObjectOutputStream stream) throws IOException{
//TODO }
private void readObject(ObjectInputStream stream) throws IOException , ClassNotFoundException{ //TODO }
这两个方法看似简单,但是其实包含了一些比较有意思的处理。首先,这个方式不是Serializable接口或者基类中的一部分,所以必须在类内部自己实现。其次,注意到这个方式其实是private类型。也就是说这两个方法仅能被这个类的其他成员调用,但是我们看到,其实我们没有在这个类的其他的方法中调用这两个方法。那么到底是谁调用这两个方法呢?其实,实际上我们并没有从这个类的其他方法中调用它们,而是ObjectOutputStream和ObjectInputStream对象的writeObject和readObject()方法分别调用者两个方法(通过过反射机制来访问类的私有方法)。
在调用ObjectOutputStream.writeObject()时,会检查所传递的Serializable对象,利用反射来搜索是否有writeObject()方法。如果有,就会跳过正常的序列化过程,转而调用这个它的writeObject()方法,readObject方法处理方式也一样。
这里面有一个技巧,在类的writeObject()内部,可以通过ObjectOutputStream.defaultWriteObject()来执行默认的writeObject()(非transient字段由这个方法保存),同样的,在类readObject内部,可以通过ObjectInputStream.defalutReadObject()来执行默认的readObject()方法。
下面这段代码就显示上述的技巧
package test.serializable; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; public class SeriCtrol implements Serializable { private static final long serialVersionUID = -4994939941552821559L; private String a; private String b; private transient String c; public SeriCtrol(String a,String b,String c) { this.a = "非瞬时默认实现:" + a; this.b = "非瞬时非默认实现:"+ b; this.c = "瞬时实现:" + c; } private void writeObject(ObjectOutputStream stream) throws IOException{ stream.defaultWriteObject(); stream.writeObject(b); stream.writeObject(c); } private void readObject(ObjectInputStream stream) throws IOException , ClassNotFoundException{ stream.defaultReadObject(); stream.readObject(); b= "null"; c = (String)stream.readObject(); } public String toString() { return a + b + c; } public static void main(String[] args) throws IOException, ClassNotFoundException { SeriCtrol sCtrol = new SeriCtrol("test1","test2","test3"); System.out.println("序列化之前"); System.out.println(sCtrol); ByteArrayOutputStream out = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(out); oos.writeObject(sCtrol); System.out.println("反序列化操作之后"); ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray()); ObjectInputStream ois = new ObjectInputStream(in); SeriCtrol src = (SeriCtrol) ois.readObject(); System.out.println(src); } }
运行结果如下:
序列化之前
非瞬时默认实现:test1非瞬时非默认实现:test2瞬时实现:test3
反序列化操作之后
非瞬时默认实现:test1null瞬时实现:test3
从结果中可以看到,字段a没做什么处理,正常序列化,b在readObject()时,将其设置为null,相当于没有序列化了,而瞬时的c,我们可以手动的将其序列化。
注意,个人在操作的过程发现,writeObject()和readObject()方法不需要成对出现,但是这时候readObject()要小心,如果没有writeObject()方法,在readObject()方法就不能再调用ObjectInputStream的readObject()方法。
想想老刘在处理超链接废弃属性时处理,在readObject()方法中,将废弃的属性置为null.
在“Thinking in Java”中提到如果想序列化static静态字段,必须自己手动去实现(p585),回头有时间,写个程序验证下。