Serializable接口与serialVersionUID

一个类实现Serializable接口后可以被序列化。这个接口没有方法和字段,只是用来标志这个类可以被序列化。

如果父类实现了serializable接口,那么子类实现还是不实现接口都一样。子类和父类所有的非static,非transient的字段的值都能被保存和恢复。

如果父类没有实现serializable接口,那么父类必须有无参的且可被子类访问的构造函数,但是不会保存父类的所有字段的值。

在反序列化的时候,没有实现serializable接口的类将会调用无参的构造函数进行实例化。而无参的构造函数必须可以被子类(进行反序列化的类)访问,子类的字段都会被恢复。
如果父类不实现 Serializable接口的话,就需要有默认的无参的构造函数。这是因为一个 Java 对象的构造必须先有父对象,才有子对象,反序列化也不例外。在反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象。因此当我们取父对象的变量值时,它的值是调用父类无参构造函数后的值。在这种情况下,在序列化时根据需要在父类无参构造函数中对变量进行初始化,否则的话,父类变量值都是默认声明的值,如 int 型的默认是 0,string 型的默认是 null。

如果需要在序列化和反序列化时做特殊处理,那么类中需要有这几个方法

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

writeObject可以记录一个类的状态信息用于readObject恢复。在这个方法中可以调用out.defaultWriteObject来记录默认的类信息。

如果被序列化的类中有Object writeReplace()方法,那么在序列化的时候,会被调用。用来替换当前类

读取一个类的class信息,类签名,非transient的,非静态的字段值。

objectInputStream.readObject();

父类:

public class ParentPer  {
    private String p1;
    private static String p2;

    protected int p3;
    public int p4;
    ParentPer(){
        System.out.println("父类构造方法执行了");
    }
    @Override
    public String toString() {
        return p1+"_"+p2+"_"+p3+"_"+p4;
    }
    public String getP1() {
        return p1;
    }

    public void setP1(String p1) {
        this.p1 = p1;
    }

    public static String getP2() {
        return p2;
    }

    public static void setP2(String p2) {
        ParentPer.p2 = p2;
    }
}

子类:

public class ChildPtr extends ParentPer implements Serializable{
    private String c1;
    private static String c2;

    protected int c3;
    public int c4;

    public ChildPtr(String c1, String c2) {
        super();
        this.c1 = c1;
        ChildPtr.c2 = c2;
        super.setP1("p1");
        setP2("p2");
        p3=5;
        p4=6;
    }

    @Override
    public String toString() {
        return c1+"_"+c2+"_"+c3+"_"+c4+" "+super.toString();
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        ChildPtr childPtr = new ChildPtr("c1", "c2");
        childPtr.c3=9;
        childPtr.c4=10;
        byte[] c = SerializableUtil.serialiable(childPtr);

        ByteArrayInputStream bi = new ByteArrayInputStream(c);
        ObjectInputStream objectInputStream = new ObjectInputStream(bi);
        ChildPtr cc = (ChildPtr) objectInputStream.readObject();
        System.out.println(childPtr);
        System.out.println(cc);
    }
}

serialVersionUID字段表示类的序列化版本,用于反序列化时校验。如果反序列化时的类的serialVersionUID与序列化时不同,那么会抛出InvalidClassException异常。
必须是final和static修饰的,推荐使用private修饰,因为它不需要被继承使用,只在序列化和反序列化时使用。
ANY-ACCESS-MODIFIER static final long serialVersionUID = 42L;

如果类中没有这个字段,那么在运行时jvm会帮忙计算一个值。推荐用户给每一个有序列化能力的类明确指定一个serialVersionUID 。因为默认的计算方式是严重依赖于编译器的实现,可能导致反序列化的时候抛出InvalidClassException异常。

数组类型不能明确指定serialVersionUID,所以它们使用默认的计算值,但是反序列化的时候不需要校验serialVersionUID。

猜你喜欢

转载自blog.csdn.net/ljz2016/article/details/83623558