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),回头有时间,写个程序验证下。

 

 

posted @ 2013-04-06 15:26  chenfei0801  阅读(1600)  评论(0编辑  收藏  举报