详解Serializable

JAVA 39 2018-08-15 12:09

  就个人理解而言, 序列化是对象的一种固化方式, 它实现了对象的持久化存储(当然实现方式不同,结果会有所差异).而Android存在两种序列化的方式, 一种基于IO流的序列化, 实现简单, 仅需实现Serializable接口而已, 可以不用添加额外的方法,源自Java; 另一种就是Android的原生实现方式了, 实现Parcelable接口了, 相比Serializable方式,实现起来较为复杂.下面将详细介绍两种实现方式, 以及它们被我们忽略掉的一些地方.

在GitHub工程 link 中可以找到相关代码

Serializable

先放出它的官方文档 : Interface Serializable

官方文档大致解决了以下几个问题:

  • 如何序列化一个类?

    通过实现一个Serializable接口(当然了,继承自Serializable的接口也算在内), 或者继承自一个实现了Seriaizable的类.

  • 继承自一个没有实现Serializable接口的父类的类序列化时, 应该注意哪些事项?

    在此种情况下, 父类必须要有一个显示的无参构造函数,而且其无参构造函数必须是能够对其子类可见的, 比如不能使用 private 修饰, 否则会抛出异常. 其原因在于, 在反序列化的过程中, 父类的变量会通过父类的无参构造函数进行初始化(==按照文档的意思, 似乎无参构造函数必须申明为public/protected, 但我实际试了下, 不使用也可以, 只要保证子类能正常访问就行==)

  no valid constructor
  • 如果序列化的对象引用了非静态的且不能进行序列化的对象时, 在序列化时会发生什么情况以及相关的处理措施?

    如果 不能进行序列化的对象未使用 transient 关键词进行修饰, 则会抛 NotSerializableException .

    这种情况下, 就需要对这个类进行一些特殊的处理了, 需要实现以下三个方法.

 private void writeObject(java.io.ObjectOutputStream out)
     throws IOException
     
 private void readObject(java.io.ObjectInputStream in)
     throws IOException, ClassNotFoundException
     
 private void readObjectNoData()throws ObjectStreamException

下面分别介绍一下这几个方法

private void writeObject(java.io.ObjectOutputStream out) throws IOException

writeObject 方法可以写入一些无法通过默认机制序列化的对象, 并使用 readObject 方法还原回来, 其实就是一个自定义序列化的方式. 通过执行 out.defaultWriteObject 方法可以保存默认参与序列化的字段, 可以使用写入基础数据类型的方法 (例如: out.writeInt)保存特殊的字段.

private void readObject(java.io.ObjectInputStream in)
     throws IOException, ClassNotFoundException

readObject 方法实际上就是一个反序列化的方法, 通过 in.defaultReadObject 方法 恢复默认的参与序列化的字段, 通过 类似于 in.readInt 等方式恢复特殊的字段.

private void readObjectNoData()throws ObjectStreamException

这个方法就很少用到了, 不必去过于纠结, 如果有好的理解, 欢迎分享出来.从它的命名上就能看出来, 这是出现了序列化的流没有给出想要的数据时出现的情况.根据官方文档所述, readObjectNoData 是为了应对 当序列化的流没有给出指定的被序列化的对象的父类的时候出现的.可能翻译得不大准确, 但大概意思就是说, 对象序列化后的流在被反序列化时发现对象的父类发生变动了(这个解释是不大准确的).这种情况出现在 接收部分使用了与发送部分不同版本的反序列化实例的类, 并且接收版本的类并没有被发送版本所继承;或者是序列化流被篡改了. 因此, readObjectNoData 适用于初始化反序列化对象, 尽管流不完整或者不那么对头

为什么说上述的理解是有问题的呢?原因在于, 我无法根据以上描述来实现调用 readObjectNoData 的场景. 我在 stackoverflow 上找到了一个相关的场景, 并大致整理了一个例子, 由于比较难以搞清楚, 所以贴出源码.

例子

序列化时的代码

package th.algorithm.practice;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.HashMap;

/**
 * Created by me_touch on 18-4-12.
 *
 */

public class SerializableTest implements Serializable{

    private B b;

    public SerializableTest(){
        this.b = new B();
    }

    public void setB(int value) {
        this.b.setB(value);
        this.b.setA(value + 1);
        System.out.println(b.getB() + b.getA());
    }

    public void trySerialB(){
        try {
            File file = new File("algorithm/B.tmp");
            if(file.exists())
                file.delete();
            file.createNewFile();
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
            oos.writeObject(b);
            oos.flush();
            oos.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public void tryDeSerialB(){
        try {
            File file = new File("algorithm/B.tmp");
            if(file.exists()) {
                ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
                B b = (B)ois.readObject();
                //System.out.println(b.getB() + b.getC());
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args){
        SerializableTest test = new SerializableTest();
        test.setB(5);
        test.trySerialB();
    }

    public static class B extends A implements Serializable{

        private int b;

        public B(){
        }

        public B(int a){
        }

        public void setB(int b) {
            this.b = b;
        }

        public int getB() {
            return b;
        }

        private void writeObject(java.io.ObjectOutputStream out) throws IOException {
            out.defaultWriteObject();
        }

        private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
            in.defaultReadObject();
        }

    }

    public static class A{

        private int a;

        /**
         * 若想子类可序列化, 则必须存在子类可访问的无参构造函数
         */
        A(){
           this.a = 0;
        }

        /**
         * 不能使用 private 修饰, 对子类不可访问
        private A(){

        }

         */
        public A(int a){
            this.a = a;
        }

        public void setA(int a) {
            this.a = a;
        }

        public int getA() {
            return a;
        }
    }
}

现在我们变更类A, 并直接进行反序列化操作, 修改过后的代码如下

package th.algorithm.practice;

import java.io.File;
import java.io.FilereadObjectNoData has been invoked
13InputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.HashMap;

/**
 * Created by me_touch on 18-4-12.
 *
 */

public class SerializableTest implements Serializable{

    private B b;

    public SerializableTest(){
        this.b = new B();
    }

    public void setB(int value) {
        this.b.setB(value);
        this.b.setA(value + 1);
        System.out.println(b.getB() + b.getA());
    }

    public void trySerialB(){
        try {
            File file = new File("algorithm/B.tmp");
            if(file.exists())
                file.delete();
            file.createNewFile();
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
            oos.writeObject(b);
            oos.flush();
            oos.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public void tryDeSerialB(){
        try {
            File file = new File("algorithm/B.tmp");
            if(file.exists()) {
                ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
                B b = (B)ois.readObject();
                System.out.println(b.getB() + b.getC());
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args){
        SerializableTest test = new SerializableTest();
        test.tryDeSerialB();
    }

    public static class B extends A implements Serializable{

        private int b;

        public B(){
        }

        public B(int a){
        }

        public void setB(int b) {
            this.b = b;
        }

        public int getB() {
            return b;
        }

        private void writeObject(java.io.ObjectOutputStream out) throws IOException {
            out.defaultWriteObject();
        }

        private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
            in.defaultReadObject();
        }

    }

    public static class A implements Serializable{

        private int a;
        private int c;

        /**
         * 若想子类可序列化, 则必须存在子类可访问的无参构造函数
         */
        A(){
           this.a = 0;
        }

        /**
         * 不能使用 private 修饰, 对子类不可访问
        private A(){

        }

         */
        public A(int a){
            this.a = a;
        }

        public void setA(int a) {
            this.a = a;
        }

        public int getA() {
            return a;
        }

        public void setC(int c) {
            this.c = c;
        }

        public int getC() {
            return c;
        }

        private void readObjectNoData()throws ObjectStreamException{
            setA(6);
            setC(8);
            System.out.println("readObjectNoData has been invoked");
        }
    }
}

执行结果

readObjectNoData has been invoked
13

以上就是关于这三个方法的大致介绍, 自定义序列化的过程中 readObjectNoData 不一定需要自己去实现, 而 writeObject 和 readObject 方法则是需要去实现的.

  • 如何使用特定的对象来替换序列化或者反序列化后的对象

    需要根据需求实现下列方法

 /**
  * 序列化时替换
  * 即可以使用private, protected 等权限关键词(子类的访问权限 follow java 本身的规则)
  */
 ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
 
 /**
  * 反序列化时替换
  * 可以用来解决单例被反序列化时带来的安全问题(即单例不再是单例)
  * 即可以使用private, protected 等权限关键词(子类的访问权限 follow java 本身的规则)
  */
 ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;

如果在序列化的过程中使用了 writeReplace 方法, 则反序列化的过程会调用用作替代 object 的反序列化方式, 而不是原 object 的序列化方式.

父类在序列化,或者反序列化的过程中, 使用了这两个中对应的方法, 则子类也会在序列化或者反序列的过程中使用这两个中对应的方法.

例子

package th.algorithm.serial;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;

/**
 * Created by me_touch on 18-4-18.
 *
 */

public class SerializableReplaceTest {

    private void trySerialInstance(){
        try {
            System.out.println(SingleInstance.INSTANCE);
            File file = new File("algorithm/files/instance.tmp");
            if(file.exists())
                file.delete();
            file.createNewFile();
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
            oos.writeObject(SingleInstance.INSTANCE);
            oos.flush();
            oos.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    private void tryDeSerialInstance(){
        try {
            File file = new File("algorithm/files/instance.tmp");
            if(file.exists()) {
                ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
                Object object = ois.readObject();
                System.out.println(object);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    private void trySerialChild(){
        Child replace = new Child();
        replace.setA(9);
        try {
            File file = new File("algorithm/files/child.tmp");
            if(file.exists())
                file.delete();
            file.createNewFile();
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
            oos.writeObject(replace);
            oos.flush();
            oos.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    private void tryDeSerialChild(){
        try {
            File file = new File("algorithm/files/c.tmp");
            if(file.exists()) {
                ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
                Object object = ois.readObject();
                System.out.println(object);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    private void trySerial(){
        WriteReplace replace = new WriteReplace();
        replace.setA(9);
        try {
            File file = new File("algorithm/files/c.tmp");
            if(file.exists())
                file.delete();
            file.createNewFile();
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
            oos.writeObject(replace);
            oos.flush();
            oos.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    private void tryDeSerial(){
        try {
            File file = new File("algorithm/files/c.tmp");
            if(file.exists()) {
                ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
                Object object = ois.readObject();
                System.out.println(object);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args){
        SerializableReplaceTest test = new SerializableReplaceTest();
        test.trySerial();
        test.tryDeSerial();
        test.trySerialChild();
        test.tryDeSerialChild();
        test.trySerialInstance();
        test.tryDeSerialInstance();
    }

    public static class Child extends WriteReplace{

    }

    public static class WriteReplace implements Serializable{
        private int a;

        public void setA(int a) {
            this.a = a;
        }

        public int getA() {
            return a;
        }

        private Object writeReplace() throws ObjectStreamException{
            return new Common();
        }
    }

    public static class Common implements Serializable{

        private transient int a;

        public  Common(){
            this.a = 1;
        }

        public int getA() {
            return a;
        }

        private void writeObject(java.io.ObjectOutputStream out) throws IOException {
            out.defaultWriteObject();
            out.writeInt(a);
        }

        private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
            in.defaultReadObject();
            in.readInt();
            System.out.println("Common readObject has been invoked");
        }

    }

    public static class SingleInstance implements Serializable{

        public static SingleInstance INSTANCE = new SingleInstance();

        private transient int a;

        private  SingleInstance(){
            this.a = 1;
        }

        public int getA() {
            return a;
        }

        private void writeObject(java.io.ObjectOutputStream out) throws IOException {
            out.defaultWriteObject();
            out.writeInt(a);
        }

        private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{
            in.defaultReadObject();
            in.readInt();
            System.out.println("SingleInstance readObject has been invoked");
        }

        private Object readResolve() throws ObjectStreamException{
            return INSTANCE;
        }
    }
}

运行结果

Common readObject has been invoked
th.algorithm.serial.SerializableReplaceTest$Common@27d6c5e0
Common readObject has been invoked
th.algorithm.serial.SerializableReplaceTest$Common@3feba861
th.algorithm.serial.SerializableReplaceTest$SingleInstance@5b480cf9
SingleInstance readObject has been invoked
th.algorithm.serial.SerializableReplaceTest$SingleInstance@5b480cf9
  • 如何理解 serialVersionUID

    serialVersionUID 既可以自动创建, 也可以手动赋值.其目的在于判断序列化和反序化的对象对应的类是否兼容.如果对应的serialVersionUID不同, 则在反序列化的过程中抛出 InvalidClassException.

    通过在序列化类中以如下方式定义 field , 可以手动指定 serialVersionUID

 ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;

需要注意的是, 由于自动计算 serialVersionUID 的方式对类的变化高度敏感, 甚至某种程度上会依赖于 Java compiler, 所以手动指定一个 serialVersionUID 是非常有必要的.并且尽可能定义为 private .毕竟这不是一个能被子类继承的成员.对于 Array classes 而言, 就放弃治疗吧, 无法手动指定, 数组类存在默认计算的 serialVersionUID, 虽然它们并不会在匹配的时候用到.

例子

按照注释操作, 可以获取到异常

package th.algorithm.serial;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * ===================================================
 * 首先使用现有代码生成一个序列化后的文件
 * 然后恢复注释掉的部分(忽略掉手动指定uid的那部分代码)
 * , 并注释掉 main函数中
 * 序列化的部分, 并保留反序列化的部分, 运行.
 * ====================================================
 */

public class SerializableUidTest {

    private void trySerial(){
        try {
            File file = new File("algorithm/files/uid.tmp");
            if(file.exists())
                file.delete();
            file.createNewFile();
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
            oos.writeObject(new Uid());
            oos.flush();
            oos.close();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    private void tryDeSerial(){
        try {
            File file = new File("algorithm/files/uid.tmp");
            if(file.exists()) {
                ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
                Object object = ois.readObject();
                System.out.println(object);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args){
        SerializableUidTest test = new SerializableUidTest();
//        test.trySerial();
        test.tryDeSerial();
    }

    public static class Uid implements Serializable{

//        private static final long serialVersionUID = 42L;

        private int a;
        private int c;

        public Uid(){
            this.a = 6;
        }
    }
}

反序列化后的异常

java.io.InvalidClassException: th.algorithm.serial.SerializableUidTest$Uid; local class incompatible: stream classdesc serialVersionUID = 4997188412835492989, local class serialVersionUID = 7081360090031566712

可以看出, UID发生了明显的变化.若手动指定UId, 重复上述操作, 则不会出现异常.

文章评论

2 关注 / 24 粉丝

我无话可说

广告已过期