JAVA序列化结论验证

static字段不会被序列化

//这是父类
class Data implements Serializable{
    private int n;
    public Data(int n){
        this.n = n;
    }
    public String toString(){
        return Integer.toString(n);
    }
}
//这是子类,继承父类自动继承Serializable接口
class sub extends Data{
    private static int m = 6;
    private int k = 10;
    public sub(int n) {
        super(n);
    }
}

父类Data有一个n的属性,通过构造器赋值
子类sub有额外的静态属性m,非静态属性k=10

父类属性 子类属性
父类Data n
子类sub 继承来的n 静态m=6,非静态k=10

假设我们构造一个子类对象 new sub(5)
那么当前子类对象的属性为

父类属性 子类属性
父类Data n = 5
子类sub 继承来的n=5 静态m=6,非静态k=10

按照序列化的结论,静态属性不会被序列化,那么猜测序列化再反序列化之后属性应该为

父类属性 子类属性
父类Data n
子类sub 继承来的n(未初始化,默认为0) 静态m=6,非静态k=10

这里我省略了序列化和反序列化的过程,直接上代码结果


序列化结果

这里只有k 和 n 因为 n没有初始化 所以不显示,所以第一个结论验证成功,这是当然的,因为序列化只是为了序列化对象的状态
tip:如果在一个JVM存活周期内,在本地JVM进行序列化和反序列化,是有可能读到n=10时的,这个n=10不是序列化得到的,而是JVM在方法区找到new出来对象时的n

反序列化什么时候需要一个空参构造器

这部分篇幅较长,涉及到源码部分,有兴趣的可以查看我的另一篇文章
从源码解析JAVA序列化是否需要空参构造方法

序列化会递归域对象的序列化

这个验证看起来可能会有点吃力,我用的《thinking in java》上的例子

class Data implements Serializable{
    private int n;
    public Data(int n){
        this.n = n;
    }
    public String toString(){
        return Integer.toString(n);
    }
}
public class Worm implements Serializable{
    private static Random rand = new Random(47);
    private Data[] d = {
            new Data(rand.nextInt(10)),
            new Data(rand.nextInt(10)),
            new Data(rand.nextInt(10))
    };
    private Worm next;
    private char c;

    public Worm(int i,char x){
        System.out.println("Worm constructor: " + i);
        c = x;
        if(-- i > 0){
            next = new Worm(i , (char)(x+1));
        }
    }
    public Worm(){
        System.out.println("default constructor");
    }

    public String toString(){
        StringBuilder result = new StringBuilder(":");
        result.append(c);
        result.append("(");
        for(Data dat : d)
            result.append(dat);
        result.append(")");
        if(next != null)
            result.append(next);
        return result.toString();
    }

  //序列化和反序列化调用的方法入口
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Worm w =new Worm(6,'a');
        System.out.println("w = " + w);
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("worm.out"));
        out.writeObject("Worm storage\n");
        out.writeObject(w);
        out.close();
        ObjectInputStream in = new ObjectInputStream(new FileInputStream("worm.out"));
        String s = (String)in.readObject();
        Worm w2 = (Worm) in.readObject();
        System.out.println(s + "w2 = " + w2);
    }
}

简单描述一下,Worm(蠕虫)类似一个链表的结构,有一个next指针指向下一个Worm,而每一个Worm有三个Date是随机生成的,toString方法会将Worm及Worm链接的next都打印出来,这里我们指定new Worm(6);
使用调试工具



序列化前的worm,有6个节点,每个节点的data都是随机生成的,而序列化会保存域的对象,已经递归的序列化的对象,那么我们猜测反序列化回来的w应该也是同样的结构,直接上结果



w2是反序列化回来的结果,可以看出不仅对象的域相同,对象的next对象都同样序列化了

不指定序列号,编译器会自动生成一个UID

还是用老朋友,data类

class Data implements Serializable{
    private int n;
    public Data(int n){
        this.n = n;
    }
    public String toString(){
        return Integer.toString(n);
    }
}

可以看到没有指定序列号,没有指定序列号的时候,编译器会自动根据Class哈希出一个UID,这样只要对类稍微改动则版本就会发生改变

        Data w = new Data(10);
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("worm.out"));
        out.writeObject(w);
        out.close();

这里用旧Data类持久化一个对象,然后我们对Data类稍微小改造,增加一个字段k

class Data implements Serializable{
    private int k;//新增加的字段
    private int n;
    public Data(int n){
        this.n = n;
    }
    public String toString(){
        return Integer.toString(n);
    }
}

按照推测,这个Data的UID已经发生变化,反序列化时因为UID与持久化对象的UID不同,会丢出InvalidClassException异常而反序列失败,我们上结果

Exception in thread "main" java.io.InvalidClassException: main.Data; local class incompatible: stream classdesc serialVersionUID = -1762858249998764225, local class serialVersionUID = -2944979104297389356
    at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1630)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1781)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
    at main.Worm.main(Worm.java:76)

验证成功了,说明确实编译器会为没有UID的对象根据类文件自动哈希一个UID,以便接受方验证类版本是否发生变化,但是不推荐不注明UID,因为自动编译的UID不可控,可能两个类文件结构相同,但是格式具有区别,或者是平台的区别,导致UID不同序列化失败,因此我们应该自己维护一个UID,并且每次对类修改时对UID进行更新。

指定类版本UID,但发送方和接收方类UID不相同时,可以序列化

序列化和反序列化时只会根据UID来验证类的一致性,因此有趣的是,即使序列化和反序列化的类文件结构不同,但是只要UID一样,也能够序列化成功,但是会丢失数据。
我们分情况验证:

  1. 序列化的类比反序列化的类多一条字段
    还是Data,序列化的Data多出来一个k字段
class Data implements Serializable{
private static final long serialVersionUID = 1L;//这里指定了UID
    private int k = 10;//比反序列化的Data多的字段
    private int n;
    public Data(int n){
        this.n = n;
    }
    public String toString(){
        return Integer.toString(n);
    }
}

反序列化的Data没有k

class Data implements Serializable{
    private static final long serialVersionUID = 1L;//这里指定了UID
    //private int k = 10;
    private int n;
    public Data(int n){
        this.n = n;
    }
    public String toString(){
        return Integer.toString(n);
    }
}

然后我们将Data序列化


有两个字段k=10,n=10;
那么反序列能成功吗,反序列之后k还存在吗


这里反序列化成功,并且反序列化之后k被丢弃了,这里我们可以得出结论:
序列化UID相同时,即使类版本不同,也能够序列化成功,序列化前多出来的字段会被丢弃

  1. 反序列化的类比序列化的类多一条字段
    这里不贴详细代码了,只要把上面两个类相反就可以了
    最后得出的结论是:
    序列化UID相同时,即使类版本不同,也能够序列化成功,反序列化多出来的字段不会得到赋值

发送方序列化子类类型,接受反反序列化用父类也可以接受

举个例子

class Data implements Serializable{
    private int n;
    public Data(int n){
        this.n = n;
    }
    public String toString(){
        return Integer.toString(n);
    }
}

class sub extends Data{
    private static int m = 6;
    private int k;
    public sub(int n) {
        super(n);
    }
}

sub继承Data,序列化时我将子类sub的一个对象序列化,反序列化时我用父类Data接受,反序列化的结果会自动向上转型吗
首先序列化一个子类sub对象k=0,n=10



然后反序列化我会用Data去接收

        ObjectInputStream in = new ObjectInputStream(new FileInputStream("worm.out"));
        Data w2 = (Data) in.readObject();//接受序列化的子类Sub对象

序列化成功,同时通过debug发现,即时用Data接收,w2对象仍然是一个sub类型



为什么会这样呢,我们打开序列化的文件

¬í �sr main.subbÊ��uéªP� �I �kxr   main.Dataç��Âût÷?� �I �nxp   

有乱码但是不影响我们观察,我们可以看到序列化时,不仅序列化了对象域的值,同时会指明序列化的这个对象是什么,也会指明继承Serializable父类是什么,最后反序列化时总是反序列化为指明的对象
继续思考,如果反序列时用Data接收,那么比较UID时是比较Data的UID和sub的UID还是sub的UID和sub的UID
一定是sub的UID,不然反序列就失败了

Class文件可以序列化吗

public final class Class implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement 

可以看到Class是继承了Serializable接口的,毋庸置疑是可以的
异想天开一下,可不可以通过Class的序列化传输一个可序列化的类,然后传输这个类的对象,接受方反序列得到本来没有的Class,然后通过这个Class反序列化这个类的对象
这里还是Data类

        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("worm.out"));
        out.writeObject(Data.class);
        Data d = new Data(10);
        out.writeObject(d);

首先把Data.class序列化,然后将Data的对象d也序列化
然后我们将Data注掉

//class Data implements Serializable{
//    private int n;
//    public Data(int n){
//        this.n = n;
//    }
//    public String toString(){
//        return Integer.toString(n);
//    }
//}

现在尝试接受Data.class和Data的对象

        ObjectInputStream in = new ObjectInputStream(new FileInputStream("worm.out"));
        Class date = (Class) in.readObject();
        in.readObject();
Exception in thread "main" java.lang.ClassNotFoundException: main.sub
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at java.lang.Class.forName0(Native Method)
    at java.lang.Class.forName(Class.java:348)
    at java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:628)
    at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1620)
    at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1521)
    at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1781)
    at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1353)
    at java.io.ObjectInputStream.readObject(ObjectInputStream.java:373)
    at main.Worm.main(Worm.java:76)

发现是不能序列化的,这里笔者回头看了一下Class文件的域,发现大多数都是static和transient,因此序列化Class对象没有什么意义
那我们就不能通过网络传输Class了吗
其实是可以的,但是需要借助自定义的ClassLoader,通过socket传输class的字节码文件,委托给自定义ClassLoader加载,就能够实现远程类加载

你可能感兴趣的:(JAVA序列化结论验证)