Java 序列化与继承

问:父类实现了 Serializable 接口后如何不修改父类的情况下让子类不可序列化?

答:这个题目其实违反了里氏替换原则(子类对象可以无条件的替换父类对象),不过面试官可能是想考察系列化的自定义操作,所以下面给出一个答案,如下:

class SubClass extends SuperSerializableClass {
    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
        throw new NotSerializableException("XXX");
    }

    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
        throw new NotSerializableException("XXX");
    }

    private void readObjectNoData() throws ObjectStreamException {
        throw new NotSerializableException("XXX");
    }
}

让子类在writeObject、readObject、readObjectNoData方法中抛出NotSerializableException()。

问:子类实现序列化接口与父类实现序列化接口有什么区别吗?

答:子类实现 Serializable 接口而父类未实现时,父类不会被序列化,也不会报序列化错误,但是如果父类没有默认构造方法则在反序列化时会出异常;父类实现序列化则子类会自动实现序列化而不需要显式实现 Serializable 接口。

这个问题会引导出一个新问题:序列化与反序列化时子类和父类构造方法是怎么调用的?

这个的答案解析就得看下面的实战例子了,如下:

class Base1 implements Serializable {
    public Base1() {
        System.out.println("Base1");
    }
}

class Child1 extends Base1 {
    public Child1() {
        System.out.println("Child1");
    }
}

class Base2 {
    public Base2() {
        System.out.println("Base2");
    }
}

class Child2 extends Base2 implements Serializable {
    public Child2() {
        System.out.println("Child2");
    }
}

public class Main {
    public static void main(String[] args) throws IOException, Exception {
        ObjectOutputStream oos1 = new ObjectOutputStream(new FileOutputStream("obj1.dat"));
        Child1 oosChild1 = new Child1();
        oos1.writeObject(oosChild1);
        oos1.flush();
        oos1.close();
        //重点
        ObjectInputStream ois1 = new ObjectInputStream(new FileInputStream("obj1.dat"));
        Child1 oisChild1 = (Child1) ois1.readObject();
        System.out.println(oisChild1);
        ois1.close();
        ObjectOutputStream oos2 = new ObjectOutputStream(new FileOutputStream("obj2.dat"));
        Child2 oosChild2 = new Child2();
        oos2.writeObject(oosChild2);
        oos2.flush();
        oos2.close();
        ObjectInputStream ois2 = new ObjectInputStream(new FileInputStream("obj2.dat"));
        Child2 oisChild2 = (Child2) ois2.readObject();//新建了一个Base2
        System.out.println(oisChild2);
        ois2.close();
    }
}

如上代码的输出如下:
Base1
Child1
Child1@1c64a584
Base2
Child2
Base2
Child2@70501e4e

所以序列化中子类和父类构造方法的调用规则如下:
序列化时子类递归调用父类的构造函数,反序列化作用于子类对象时如果其父类没有实现序列化接口则其父类的默认无参构造函数会被调用,如果父类实现了序列化接口则不会调用构造方法。但是如果上面 Base2 不存在默认构造方法就会出错,如下:

        Exception in thread "main" java.io.InvalidClassException:Child2;
        no valid constructor at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(Unknown Source) at java.
        io.ObjectStreamClass.checkDeserialize(Unknown Source) at java.
        io.ObjectInputStream.readOrdinaryObject(Unknown Source) at java.io.ObjectInputStream.readObject0(Unknown Source)
        at java.io.ObjectInputStream.readObject(Unknown Source)

所以要想将父类对象也序列化就需要让父类也实现 Serializable 接口,如果父类没有实现就需要有默认的无参构造方法,因为在父类没有实现 Serializable 接口时虚拟机是不会序列化父对象的,而一个 Java 对象的构造必须先有父对象才有子对象,反序列化也不例外,所以反序列化时为了构造父对象则只能调用父类的无参构造函数作为默认的父对象,故当我们取父对象的变量值时都是无参构造方法中初始化的值。

问:父类没有实现 Serializable接口怎么被序列化?

答:因为在父类没有实现 Serializable 接口时虚拟机是不会序列化父对象的(即序列化后的流中没有父类信息),而一个 Java 对象的构造必须先有父对象才有子对象,反序列化也一样,所以反序列化时为了构造父对象只能调用父类的无参构造方法作为默认的父对象,故要让一个没有实现 Serializable 接口的父类能够序列化则必须要求父类有无参构造方法且子类负责序列化(反序列化)父类的域。如下给出实现样例:

        abstract class Base {
            public int value;

            public Base(int value) {
                this.value = value;
            }

            public Base() {
            }
        }
        class Sub extends Base implements Serializable {
            public int subvalue;

            public Sub(int value, int subvalue) {
                super(value);
                this.subvalue = subvalue;
            }

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

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

所以为一个没有实现 Serializable 接口的父类编写一个能够完全序列化的子类需要父类有一个无参构造方法且子类要先序列化自身,然后子类负责序列化父类的域。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • JAVA序列化机制的深入研究 对象序列化的最主要的用处就是在传递,和保存对象(object)的时候,保证对象的完整...
    时待吾阅读 13,769评论 0 24
  • 1.import static是Java 5增加的功能,就是将Import类中的静态方法,可以作为本类的静态方法来...
    XLsn0w阅读 5,127评论 0 2
  • 面向对象主要针对面向过程。 面向过程的基本单元是函数。 什么是对象:EVERYTHING IS OBJECT(万物...
    sinpi阅读 4,822评论 0 4
  • 一、 序列化和反序列化概念 Serialization(序列化)是一种将对象以一连串的字节描述的过程;反序列化de...
    步积阅读 5,282评论 0 10
  • 随着长大对知识的追求的热情慢慢在降低,兴趣爱好都在减弱,一直被称为学霸,然而并不认为自己是学霸,索性把自己划在中间...
    静默行走阅读 3,430评论 0 0

友情链接更多精彩内容