为什么在反序列化过程中不调用默认构造函数?

ObjectInputStream is = new ObjectInputStream(new FileInputStream("test.ser")); TestClass tc = (TestClass)is.readObject(); 

我在反序列化后得到了TestClass的对象,但是没有调用TestClass的默认构造函数。 根据我的理解,有两种方法可以创建对象,即使用new运算符或TestClass.class.newInstance()。 两者都调用默认构造函数。

看起来像反序列化过程创建对象不是用两个方法,这就是为什么不调用默认构造函数。 问题是反序列化如何创建对象?

另一点是,如果TestClass扩展BaseTestClass并且BaseTestClass没有实现序列化,则调用BaseTestClass的构造函数但不调用TestClass。 为什么这样 ? 我相信它背后会有一些合理的理由。 但我没有得到它?

值得阅读Java对象序列化规范:3 – 对象输入类 ,其中详细描述了readObject方法以及逐步说明。

这个怎么运作?

分配了一个类的实例。 实例及其句柄将添加到已知对象集中。

内容适当恢复:

  1. 对于可序列化对象, 运行第一个非可序列化超类型的no-arg构造函数

    • 对于可序列化的类,字段将初始化为适合其类型的默认值。

    • 然后通过调用类特定的readObject方法来恢复每个类的字段,或者如果未定义这些方法,则通过调用defaultReadObject方法来恢复。

    • 请注意, 在反序列化期间,不会对可序列化类执行字段初始值设定项和构造函数

    • 在正常情况下,编写流的类的版本将与读取流的类相同。 在这种情况下,流中对象的所有超类型都将匹配当前加载的类中的超类型。

    • 如果编写流的类的版本具有与加载的类不同的超类型,则ObjectInputStream必须更加小心地恢复或初始化不同类的状态。

    • 它必须逐步执行这些类,将流中的可用数据与要还原的对象的类进行匹配。 流中出现但未在对象中出现的类的数据将被丢弃。

    • 对于在对象中出现但在流中不出现的类,默认序列化将类字段设置为默认值。

  2. 对于可外部化的对象,运行该类的no-arg构造函数,然后调用readExternal方法以恢复该对象的内容。


用于理解第一点的示例代码对于可序列化对象,运行第一个非可序列化超类型的无参数构造函数。

示例代码;

 class TestClass1 { public TestClass1() { System.out.println("TestClass1"); } } class TestClass2 extends TestClass1 implements Serializable { public TestClass2() { System.out.println("TestClass2"); } } public static void main(String[] args) throws Exception { System.out.println("Object construction via calling new keyword"); ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("resources/dummy.dat")); out.writeObject(new TestClass2()); System.out.println("Object construction via readObject method"); ObjectInputStream is = new ObjectInputStream(new FileInputStream("resources/dummy.dat")); TestClass2 tc = (TestClass2) is.readObject(); } 

输出:

 Object construction via calling new keyword TestClass1 TestClass2 Object construction via readObject method TestClass1 

注意:这与Externalizable类有关,而不是Serializable ,正如Pshemo在下面的评论中正确指出的那样。 Braj发布的答案显示了Serializable的代码示例。


首先,请注意默认构造函数和无参数构造函数之间的区别。 如果您不提供任何其他构造函数,则默认构造函数是生成的无参数构造函数。

ObjectInputStream要求类具有无参数构造函数,这是一个演示它的代码示例:

 import java.util.*; import java.lang.*; import java.io.*; class Ideone { static class Test implements Externalizable { //public Test() {} public Test(int x) { } public void writeExternal(ObjectOutput out) throws IOException { } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { } } public static void main(String[] args) throws java.lang.Exception { Test t = new Test(0); ByteArrayOutputStream os = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(os); oos.writeObject(t); oos.close(); ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray()); ObjectInputStream ois = new ObjectInputStream(is); t = (Test)ois.readObject(); ois.close(); } } 

生产:

线程“main”中的exceptionjava.io.InvalidClassException:Ideone $ Test; 在java.io.Object.StartEreamClass上没有有效的构造函数$ ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:147),java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1751)中的java.io.Object.StreamClass.checkDeserialize(ObjectStreamClass.java:755)在java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1347)处于Ideone.main的java.io.ObjectInputStream.readObject(ObjectInputStream.java:369)(Main.java:36)

Ideone上的演示: http ://ideone.com/yPpJrb

取消注释no-arg构造函数时,它可以正常工作。 当你删除提供的单参数构造函数时它也会正常工作 – 因为那时,将生成默认构造函数。

来自oracle文档

从ObjectInputStream读取对象类似于创建新对象。 正如新对象的构造函数按从超类到子类的顺序调用一样,从流中读取的对象从超类反序列化为子类。 在反序列化期间,将调用readObject或readObjectNoData方法而不是每个Serializable子类的构造函数。

所以简而言之,它应该在从超类到子类的层次结构中调用readObject()方法。 只有当所有超类都实现了可序列化接口时才会出现,否则会调用superclasse的默认构造函数。 如此序列化

可序列化对象的每个子类可以定义其自己的readObject方法。 如果类未实现该方法,则将使用defaultReadObject提供的默认序列化。 实现时,该类仅负责恢复其自己的字段,而不是其超类型或子类型的字段。