Why default constructor is not called while deserialization process?
1 2 | 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 方法来恢复每个类的字段,或者如果没有定义这些方法,则通过调用defaultReadObject 方法。 -
请注意,在反序列化期间,不会为可序列化类执行字段初始值设定项和构造函数。
-
在正常情况下,写入流的类的版本将与读取流的类相同。在这种情况下,流中对象的所有超类型都将匹配当前加载的类中的超类型。
-
如果编写流的类的版本与加载的类具有不同的超类型,则
ObjectInputStream 必须更加小心地恢复或初始化不同类的状态。 -
它必须逐步遍历类,将流中的可用数据与正在恢复的对象的类进行匹配。出现在流中但未出现在对象中的类的数据将被丢弃。
-
对于出现在对象中但不在流中的类,通过默认序列化将类字段设置为默认值。
对于可外部化的对象,运行类的无参数构造函数,然后调用
理解第一个点的示例代码对于可序列化对象,运行第一个不可序列化超类型的无参数构造函数。
示例代码;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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(); } |
输出:
1 2 3 4 5 6 |
来自oracle文档
Reading an object from the ObjectInputStream is analogous to creating a new object. Just as a new object's constructors are invoked in the order from the superclass to the subclass, an object being read from a stream is deserialized from superclass to subclass. The readObject or readObjectNoData method is called instead of the constructor for each Serializable subclass during deserialization.
所以简而言之,它应该在从超类到子类的层次结构中调用 readObject() 方法。仅当所有超类都实现可序列化接口时才会出现,否则将调用超类的默认构造函数。
如此可序列化
Each subclass of a serializable object may define its own readObject method. If a class does not implement the method, the default serialization provided by defaultReadObject will be used. When implemented, the class is only responsible for restoring its own fields, not those of its supertypes or subtypes.
注意:这与
的代码示例
首先,请注意默认构造函数和无参数构造函数之间的区别。如果您不提供任何其他构造函数,则默认构造函数是生成的无参数构造函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | 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(); } } |
生产:
Exception in thread"main" java.io.InvalidClassException: Ideone$Test; no valid constructor
at java.io.ObjectStreamClass$ExceptionInfo.newInvalidClassException(ObjectStreamClass.java:147)
at java.io.ObjectStreamClass.checkDeserialize(ObjectStreamClass.java:755)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1751)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1347)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:369)
at Ideone.main(Main.java:36)
Ideone 演示:http://ideone.com/yPpJrb
当您取消注释无参数构造函数时,它可以正常工作。当您删除提供的单参数构造函数时,它也可以正常工作 - 因为那样会生成默认构造函数。