深入了解java序列化

在日常开发中,前端与后端的交互,系统之间的远程调用都需要使用到序列化技术,在java中使用序列化非常简单,只需要将被序列化的对象的类实现Java.io.Serializable接口即可。
对于实现序列化接口的类,我们需要注意两点:

  1. 类中的静态变量我们是无法序列化的,因为序列化只是针对对象,而静态变量是类级别的。
  2. 当子类实现序列化接口,而父类没有实现序列化接口时,将子类进行序列化,再反序列化回来后,发现父类中的属性会被重新初始化,也就是说会调用父类的无参构造。如果没有无参构造,则会抛出异常。

下面是通过两个小案例来证明:

public class SeriaTest implements Serializable{

    private static final long serialVersionUID = 1L;

    private static Integer count = 10;

    public static void main(String[] args) {
        try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\test.obj"));
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\test.obj"))) {
            oos.writeObject(new SeriaTest());
            count = 20;
            SeriaTest seriaTest = (SeriaTest) ois.readObject();
            System.out.println(seriaTest.count);
        } catch (Exception e) {
            System.out.println("序列化反序列化时出现异常:"+e);
        }
    }
}

这段代码的运行结果为20,说明静态变量不会被序列化。

public class Fu {

    public String fu;
    public String getFu() {
        return fu;
    }
    public void setFu(String fu) {
        this.fu = fu;
    }
}

public class Zi extends Fu implements Serializable{

    private static final long serialVersionUID = 1L;
    public String zi;

    public Zi(){
        fu = "fu";
        zi = "zi";
    }
    public String getZi() {
        return zi;
    }
    public void setZi(String zi) {
        this.zi = zi;
    }
}

public class SeriaTest{

    public static void main(String[] args) {
        public static void main(String[] args) {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\test.obj"));
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\test.obj"))){
            oos.writeObject(new Zi());
            Zi zi = (Zi) ois.readObject();
            System.out.println(zi.fu);
            System.out.println(zi.zi);
        } catch (Exception e) {
        }
    }
}

这段代码的运行结果为null,zi。验证了上面的第二点。

将对象序列化实际上调用的就是ObjectOutputStream的wirteObject方法,跟踪源代码可知能被序列化的对象除了是实现Serializable接口的对象,还可以是String,数组或者枚举对象:

if (paramObject instanceof String) {
        writeString((String) paramObject, paramBoolean);
} else if (((Class) localObject2).isArray()) {
        writeArray(paramObject, localObjectStreamClass, paramBoolean);
} else if (paramObject instanceof Enum) {
        writeEnum((Enum) paramObject, localObjectStreamClass,paramBoolean);
} else if (paramObject instanceof Serializable) {
        writeOrdinaryObject(paramObject, localObjectStreamClass,paramBoolean);
}

再继续跟进writeOrdinaryObject方法,我们会发现会有这么一段逻辑:

if ((paramObjectStreamClass.isExternalizable()) && (!(paramObjectStreamClass.isProxy())))
    writeExternalData((Externalizable) paramObject);
else
    writeSerialData(paramObject, paramObjectStreamClass);

先判断是不是实现了Externalizable接口(这个是Serializable的子接口,这里先不管,后面会解释),如果不是,则执行writeSerialData方法。跟进去:

if (localObjectStreamClass.hasWriteObjectMethod()) {
    ********省略*******       
    localObjectStreamClass.invokeWriteObject(paramObject, this);
    ********省略*******   
} else {
    defaultWriteFields(paramObject, localObjectStreamClass);
}

我们发现源代码先判断了一下这个需要被序列化的类中有没有writeObject这个方法,如果有,那么就执行,如果没有,那么就执行默认的序列化方法。对于这个writeObject方法,有以下几个要求:

  • 方法名必须叫writeObject
  • 必须是私有的方法
  • 返回值类型必须为void

    只有满足了以上三点,在序列化对象的时候,才会执行我们自定义的序列化方法。当然,我们除了可以重写writeObject方法,我们还可以重写readObject,readObjectNoData,writeReplace,readResolve等方法,这些方法之间有什么联系,有什么作用呢?我们通过一段代码来探索一下:

public class SeriaTest implements Serializable{

    private static final long serialVersionUID = 1L;

    public Integer count = 10;

    public SeriaTest(Integer count){
        this.count = count;
    }

    public static void main(String[] args) {
        try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\test.obj"));
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\test.obj"))) {
            oos.writeObject(new SeriaTest(20));
            SeriaTest seriaTest = (SeriaTest) ois.readObject();
            System.out.println(seriaTest.count);
        } catch (Exception e) {
            System.out.println("序列化反序列化时出现异常:"+e);
        }
    }

    private void writeObject(ObjectOutputStream oos) throws IOException{
        System.out.println("序列化前执行了自定义的writeObject方法");
        oos.defaultWriteObject();
        System.out.println("序列化后执行了自定义的writeObject方法");
    }

    private Object writeReplace() {
        System.out.println("序列化时执行了自定义的writeReplace方法");
        SeriaTest s = new SeriaTest(10);
        return s;
    }
}

执行结果如下:

序列化时执行了自定义的writeReplace方法
序列化前执行了自定义的writeObject方法
序列化后执行了自定义的writeObject方法
10

根据执行结果我们我们可以知道,在对象序列化的时候,会先去执行被序列化的类中的writeReplace方法,再执行writeObject方法,如果重写了writeReplace方法,那么被序列化的对象就是这个方法的返回值,而writeObject方法主要作用就是在序列化前后可以做处理操作。
对应的读操作的方法分别是readResolve方法和readObject方法,将上面的代码再完善一下:

public class SeriaTest implements Serializable{

    private static final long serialVersionUID = 1L;

    public Integer count = 10;

    public SeriaTest(Integer count){
        this.count = count;
    }

    public static void main(String[] args) {
        try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\test.obj"));
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\test.obj"))) {
            oos.writeObject(new SeriaTest(20));
            SeriaTest seriaTest = (SeriaTest) ois.readObject();
            System.out.println(seriaTest.count);
        } catch (Exception e) {
            System.out.println("序列化反序列化时出现异常:"+e);
        }
    }

    private void writeObject(ObjectOutputStream oos) throws IOException{
        System.out.println("序列化前执行了自定义的writeObject方法");
        oos.defaultWriteObject();
        System.out.println("序列化后执行了自定义的writeObject方法");
    }

    private Object writeReplace() {
        System.out.println("序列化时执行了自定义的writeReplace方法");
        SeriaTest s = new SeriaTest(10);
        return s;
    }

    private void readObject(ObjectInputStream ois) throws ClassNotFoundException, IOException{
        System.out.println("反序列化前执行了自定义的readObject方法");
        ois.defaultReadObject();
        System.out.println("反序列化后执行了自定义的readObject方法");
    }

    private Object readResolve(){
        System.out.println("反序列化前执行了自定义的readReslove方法");
        SeriaTest s = new SeriaTest(30);
        return s;
    }
}

执行结果符合我们的预期:

序列化时执行了自定义的writeReplace方法
序列化前执行了自定义的writeObject方法
序列化后执行了自定义的writeObject方法
反序列化前执行了自定义的readObject方法
反序列化后执行了自定义的readObject方法
反序列化前执行了自定义的readReslove方法
30

至于readObjectNoData方法,这个比较少见,这个方法的作用就是当一个对象被序列化后,反序列化时可以添加属性。(具体可参考:readObjectNoData方法作用

下面我们再来解析一下上面提到的Externalizable接口,这个接口是Serializable接口的子接口,这个接口有两个抽象方法:writeExternal和readExternal,这个两个方法分别对应于writeObject和readObject,不同点在于writeObject和readObject都是私有方法,所以其子类不能复用并且不能复写,而writeExternal和readExternal是共有方法,其子类可以复用并且复写。

public class SeriaTest implements Externalizable{

    private String name;
    private Integer age;

    public static void main(String[] args) {
        try(ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\test.obj"));
            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\test.obj"))
            ) {
            oos.writeObject(new SeriaTest());
            SeriaTest seriaTest = (SeriaTest) ois.readObject();
            System.out.println(seriaTest.name);
            System.out.println(seriaTest.age);
        } catch (Exception e) {
            System.out.println("序列化反序列化时出现异常:"+e);
        }
    }

    @Override
    public void writeExternal(ObjectOutput paramObjectOutput)
            throws IOException {
        System.out.println("writeExternal.....");
        paramObjectOutput.writeInt(18);
        paramObjectOutput.write("zhangsan".getBytes(Charset.forName("UTF-8")));
    }

    @Override
    public void readExternal(ObjectInput paramObjectInput) throws IOException,
            ClassNotFoundException {
        System.out.println("readExternal.....");
        age = paramObjectInput.readInt();
        name = paramObjectInput.readLine();
    }

}

执行结果如下:

writeExternal.....
readExternal.....
zhangsan
18
上一篇:记录数据库中的技巧


下一篇:Asp.net mvc基础(十五)EF原理及SQL监控