Java对象序列化 Object Serialization

来源:互联网 发布:java分布式架构 编辑:程序博客网 时间:2024/05/01 14:57

在最近的一次项目中,从服务器中请求得到了登录用户的相关信息,显然的,需要把其中的信息保存下来。在Android中,持久化保存信息的方式有很多种。比如数据库,首选项,又或者内部存储。但是,这个需要保存的信息字段有些多,目的是方便没网络时也能过显示一些相关的信息。我想到了对象序列化技术,把整个对象保存到磁盘中(内部存储)。
于是,结合网上的文章,及书本上的对应章节,把该知识点记录一下。
想要把一个对象能够被直接保存到数据库或磁盘中,有以下的步骤:

①实现Serializable接口,这是一个声明接口,没有需要实现的方法

    public class Person implements Serializable {        public static final long serialVersionUID = 1L;        private int age;        private String password;        //other fields        //setter and getter    }

②保存对象

    public void saveObject(Object obj) {        FileOutputStream fos = null;        ObjectOutputStream oos = null;        try {            fos = new FileOutputStream("person.dat");            oos = new ObjectOutputStream(fos);            oos.writeObject(obj);        } catch(Excepton e) {            e.printStackTrace();        } finally {            //close stream        }    }

通过上面简单的几句代码,我们就把对象保存下来了。

③读取对象

    public Object readObject() {        FileInputStream fis = null;        ObjectInputStream ois = null;        Object obj = null;        try {            fis = new FileInputStream("person.dat");            ois = new ObjectInputStream(fis);            obj = ois.readObject();        } catch(Exception e) {            //...        } finally {            //close stream        }        return obj;    }

通过上面的方法调用我们就把保存的对象从物理媒介中读取了出来

简单的序列化调用这样即可。当然,还需要理解关键字 transient的意思,它表示当序列化时,请把我忽略;同时,静态字段static也不会被序列化。

如果我们想保存某个对象时,需要修改,加密某些字段,应该要如果做?我们当然可以额外的在在保存时加密,取出来时再解密。但序列化机制提供了这样的方法截点。即,如果声明了Serializable的类中有实现private void writeObject(ObjectOutputStream oos);则保存时会调用该方法,而不是默认的调用ObjectOutputStream#defaultWriteObject()方法;如果实现了private void readObject(ObjectInputStream ois);方法,则读取时会调用该方法,而不是默认的ObjectInputStream#defaultReadObject()方法。当然推荐的是,当我们自己实现这两个方法时,首要的是调用默认的实现。我们举个例子,我们要对Person中的password字段加密后再保存,因为当对象保存到本地时,是可以轻易的读取出来的。
例子:

    public class Person implements Serializable {        public static final long serialVersionUID = 1L;        private String password;        //other fields        //other methods        private void writeObject(ObjectOutputStream oos) {            this.password = encrypt(this.password);            try {                oos.defaultWriteObject();            } catch(Exception e) {}        }        private void readObject(ObjectInputStream ois) {            try {                ois.defaultReadObject();                this.password = decrypt(this.password);            } catch(Exception e) {}        }    }   

.
嗯,解决完这个问题。再来看一个序列化对象版本的问题。当读取一个序列号对象时,如果没有设置静态常量字段serialVersionUID,则虚拟机会根据类的相关信息动态生成一个新的版本号,这可能会导致类不兼容,引发InvalidClassException异常抛出。serialVersionUID的值可以自己随意设置,或者使用serialver工具生成,或者Eclipse中有智能提示,或者设置Android Studio自动生成。

再来看一个问题,假如我们这个对象的实例是Singleton单例模式,但是我们读取对象时是新建了一个对象,显然是不符合我们的需求的。所以,我们再看一下另一个方法private Object readResolve();这个方法也是写在声明了Serializable的类里边。该方法的特性是:

对于一个正在被反序列化的对象,如果它的类定义了一个readObject方法,并且具备正确的声明,那么在反序列化之后,新建对象上的readResolve方法就会被调用。然后,该放方法返回的对象引用将被返回,取代新对象。

所以,我们可以在该方法中返回单例对象。

.
另外,这里引申出一个简单的用法,即深度克隆一个对象:

    public Object cloneObject(Object srcObj) {        ByteArrayOutputStream bout = new ByteArrayOutputStream();        ObjectOutputStream out = new ObjectOutputStream(bout);        out.writeObject(srcObj);        out.close;        ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());        ObjectInputStream in = new ObjectInputStream(bin);        Object ret = in.readObject();        return ret;    }

.

对于简单的序列化用法,有几点总结:
①显示声明序列化版本号serialVersionUID
②保护性地编写readObject方法,比如某些字段不能为null

0 0
原创粉丝点击